Skip to content

Implement document upload UI with drag-and-drop and validation#6

Merged
JDRay42 merged 9 commits intomainfrom
copilot/create-document-upload-ui
Jan 6, 2026
Merged

Implement document upload UI with drag-and-drop and validation#6
JDRay42 merged 9 commits intomainfrom
copilot/create-document-upload-ui

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 24, 2025

Document Upload UI Implementation ✅

  • Create DocumentsController with Index, Upload, and Create actions
  • Create Documents/Index.cshtml view to list documents in active universe
  • Create Documents/Upload.cshtml view with file upload form using Shoelace
  • Create Documents/Details.cshtml view to show document and version information
  • Add file upload handling with validation (txt, md files)
  • Integrate with IIngestionService to process uploaded documents
  • Add Scripts section support to layout
  • Test the upload UI manually with screenshots
  • Address code review feedback
  • Run security checks with codeql_checker (0 vulnerabilities)
  • Add complete XML documentation for IntelliSense
  • Address accessibility, security, and UX feedback from code review

UI Screenshots

Empty Documents Page

Documents Empty

Document Upload Form

Upload Form

Upload Form with File Selected

Upload Form Filled

Recent Improvements (Code Review Feedback)

Accessibility Enhancements

  • ✅ Added keyboard navigation support to drop zone (role="button", tabindex="0", Enter/Space key handlers)
  • ✅ Increased alert timeout from 5s to 8s for better screen reader accessibility
  • ✅ Improved info button with Shoelace tooltip for better UX
  • ✅ Fixed checkbox label association for proper click handling

Security Improvements

  • ✅ Sanitized error messages to prevent sensitive information exposure
  • ✅ Generic error messages shown to users, detailed errors only logged
  • ✅ Document rollback when ingestion fails to prevent orphaned records

Validation & UX Improvements

  • ✅ Client-side file validation (type and size) on drag-and-drop
  • ✅ Form validation ensures file is selected before submission
  • ✅ Better browser compatibility for DataTransfer API with try-catch fallback
  • ✅ Drop zone click only active when file info not visible
  • ✅ Named constants for magic numbers (KB_TO_BYTES, MB_TO_BYTES, ALERT_TIMEOUT_MS, etc.)

Performance Improvements

  • ✅ Optimized segment sorting in Details view (filter before sort instead of sort all then take)

Features Implemented

Core Functionality

  • DocumentsController with full CRUD operations and complete XML documentation
  • Universe-scoped operations - documents only shown/created for active universe
  • File upload handling with proper validation
  • Integration with IIngestionService for document processing
  • Rollback mechanism when ingestion fails to avoid orphaned documents

UI/UX

  • Clean, modern design using Shoelace web components
  • Drag-and-drop file upload with visual feedback and keyboard navigation
  • Cross-browser compatible file handling using DataTransfer API with fallback
  • Client-side validation for file type (.txt/.md) and size (max 10MB)
  • Server-side validation as backup security layer
  • Empty states with helpful guidance
  • Alert notifications for success/error feedback (8s timeout for accessibility)
  • Responsive layout consistent with existing UI
  • Keyboard accessible drop zone and form controls

Document Management

  • Document metadata capture (title, type, series, book number)
  • Optional immediate ingestion toggle
  • Document listing with version counts and badges
  • Document details view with version history and segment previews

Security

  • ✅ File type validation (only .txt and .md) - client and server side
  • ✅ File size validation (max 10MB) - client and server side
  • ✅ Anti-forgery token protection
  • ✅ Universe-scoped authorization checks
  • ✅ Sanitized error messages (no sensitive data exposure)
  • ✅ No security vulnerabilities detected by CodeQL

Documentation

  • ✅ All public methods have IntelliSense-compatible XML documentation
  • ✅ Constructor parameters fully documented
  • ✅ Return types and parameters documented

Technical Implementation

Files Added/Modified

  • src/Palimpsest.Web/Controllers/DocumentsController.cs (NEW)
  • src/Palimpsest.Web/Views/Documents/Index.cshtml (NEW)
  • src/Palimpsest.Web/Views/Documents/Upload.cshtml (NEW)
  • src/Palimpsest.Web/Views/Documents/Details.cshtml (NEW)
  • src/Palimpsest.Web/Views/Shared/_Layout.cshtml (Modified - added Scripts section)

Design Patterns

  • Follows existing ASP.NET Core MVC patterns
  • Uses Clean Architecture principles
  • Consistent with Universes controller implementation
  • Integrates with existing repository and service interfaces

Error Handling

  • Validates file presence, type, and size (client and server)
  • Requires active universe selection
  • Gracefully handles ingestion failures with document rollback
  • Provides user-friendly, sanitized error messages
  • Detailed errors logged but not exposed to users

This implementation provides a solid, secure, and accessible foundation for document management in Phase 1 and is ready for use.

Original prompt

We need a clean, robust document upload UI


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 5 commits December 24, 2025 21:16
Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
…e file handling

Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
Copilot AI changed the title [WIP] Add clean document upload UI Implement document upload UI with drag-and-drop and validation Dec 24, 2025
Copilot AI requested a review from JDRay42 December 24, 2025 21:29
@JDRay42
Copy link
Copy Markdown
Owner

JDRay42 commented Dec 24, 2025

@copilot the public methods and properties need to have proper IntelliSense-compatible XML documentation

…ameters

Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 24, 2025

@copilot the public methods and properties need to have proper IntelliSense-compatible XML documentation

Added complete XML documentation with parameter descriptions to the DocumentsController constructor in commit a59583a. All public methods and properties now have proper IntelliSense-compatible documentation.

@JDRay42 JDRay42 marked this pull request as ready for review December 24, 2025 21:44
Copilot AI review requested due to automatic review settings December 24, 2025 21:44
Copy link
Copy Markdown
Owner

@JDRay42 JDRay42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive document upload UI with drag-and-drop functionality, validation, and integration with the ingestion service. The implementation adds a new DocumentsController with CRUD operations and three new Razor views for listing, uploading, and viewing document details.

Key Changes:

  • New DocumentsController with universe-scoped document operations, file upload validation (txt/md only, max 10MB), and optional ingestion triggering
  • Three new views using Shoelace components: Index (document listing with badges), Upload (drag-and-drop form), and Details (version history with segment previews)
  • Layout modification to support page-specific JavaScript via Scripts section

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/Palimpsest.Web/Controllers/DocumentsController.cs New controller with Index, Upload, and Details actions; includes XML documentation, file validation, and ingestion service integration
src/Palimpsest.Web/Views/Documents/Index.cshtml Document listing page with empty state, success/warning/error alerts, and auto-dismissal script
src/Palimpsest.Web/Views/Documents/Upload.cshtml Upload form with drag-and-drop functionality, file validation UI, and form submission handling
src/Palimpsest.Web/Views/Documents/Details.cshtml Document details view showing metadata, version history, and segment previews
src/Palimpsest.Web/Views/Shared/_Layout.cshtml Added Scripts section support for page-specific JavaScript

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +84 to +107
<div id="dropZone" style="
border: 2px dashed #cbd5e0;
border-radius: 8px;
padding: 40px;
text-align: center;
background: #f8fafc;
cursor: pointer;
transition: all 0.2s;
margin-top: 10px;">
<sl-icon name="cloud-upload" style="font-size: 48px; color: #667eea; margin-bottom: 15px;"></sl-icon>
<div style="font-size: 16px; margin-bottom: 10px;">
<strong>Drop your file here</strong> or click to browse
</div>
<div style="font-size: 14px; color: #666;">
.txt or .md files up to 10MB
</div>
<input
type="file"
id="file"
name="file"
accept=".txt,.md"
required
style="display: none;">
</div>
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop zone for file upload lacks proper accessibility. It should have keyboard navigation support for users who cannot use drag-and-drop. While the file input is keyboard accessible, the drop zone itself should have role="button", tabindex="0", and keyboard event handlers (Enter/Space) to make the click-to-browse functionality accessible to keyboard users.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added keyboard navigation support to the drop zone in commit ca4c808. The drop zone now has role="button", tabindex="0", and handles both Enter and Space key presses to trigger file browsing. This makes the upload interface fully accessible to keyboard users.

catch (Exception ex)
{
_logger.LogError(ex, "Error uploading document");
ModelState.AddModelError(string.Empty, $"An error occurred while uploading the document: {ex.Message}");
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message at line 184 exposes the full exception message to the user, which may contain sensitive information such as file paths, internal system details, or database connection strings. Error messages shown to users should be sanitized and generic, while detailed error information should only be logged.

Suggested change
ModelState.AddModelError(string.Empty, $"An error occurred while uploading the document: {ex.Message}");
ModelState.AddModelError(string.Empty, "An unexpected error occurred while uploading the document. Please try again later.");

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot error reporting should follow RFC7807 standards

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RFC7807 (Problem Details) is designed for HTTP API responses, not traditional MVC form submissions with view rendering. This application uses server-rendered views with TempData and ModelState for user feedback, which is the standard ASP.NET Core MVC pattern.

To implement RFC7807, we would need to:

  1. Add ProblemDetails middleware configuration
  2. Convert the form to use AJAX/fetch for submission
  3. Return ProblemDetails responses instead of View results
  4. Handle problem details in JavaScript client-side

This would be a significant architectural change. Could you clarify if you want to convert this to an API-style endpoint, or if the current MVC pattern with sanitized error messages is acceptable?

Comment on lines +100 to +109
<script>
// Auto-close alerts after 5 seconds
document.addEventListener('DOMContentLoaded', function() {
const alerts = document.querySelectorAll('sl-alert[open]');
alerts.forEach(alert => {
setTimeout(() => {
alert.hide();
}, 5000);
});
});
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script auto-closes alerts after 5 seconds, but this may not be enough time for screen reader users to hear the full message. Additionally, users with cognitive disabilities may need more time to read and understand the message. Consider increasing the timeout or making it configurable, and ensure screen readers announce the alerts properly.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alert timeout was already increased to 8 seconds in commit ca4c808 to improve accessibility for screen reader users.

Comment on lines +174 to +186
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#cbd5e0';
dropZone.style.background = '#f8fafc';

const files = e.dataTransfer.files;
if (files.length > 0) {
const dt = new DataTransfer();
dt.items.add(files[0]);
fileInput.files = dt.files;
displayFileInfo(files[0]);
}
});
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drag and drop implementation lacks validation for file type and size. When a file is dropped, it should validate that the file extension is .txt or .md and that the size doesn't exceed 10MB before displaying the file info. Without this client-side validation, users may drag and drop unsupported files and only discover the error after submission.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on [this feedback]. Use RFC7807 problem reporting standards where errors are risen. (#6 (comment))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client-side validation errors are handled in the browser using JavaScript alerts and form validation UI. RFC7807 (Problem Details) is a server-side HTTP response standard for APIs, not for client-side JavaScript validation.

The current implementation already validates file type and size client-side before display (added in commit ca4c808). For server-side validation errors, we use ModelState which is the standard MVC pattern.

Could you clarify what specific RFC7807 implementation you're looking for in the client-side validation context?

Comment thread src/Palimpsest.Web/Views/Documents/Upload.cshtml Outdated
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ingestion for document {DocumentId}", document.DocumentId);
TempData["Warning"] = $"Document '{title}' uploaded, but ingestion failed to start: {ex.Message}";
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The document is created and persisted to the database before attempting ingestion. If ingestion is requested but fails (caught at lines 168-172), the document remains in the database without any version or content. This creates orphaned documents that exist but have no usable data. Consider either rolling back the document creation if ingestion fails, or creating the document only after successful ingestion when startIngestion is true.

Suggested change
TempData["Warning"] = $"Document '{title}' uploaded, but ingestion failed to start: {ex.Message}";
// Roll back the created document to avoid leaving an orphaned document without content.
try
{
await _documentRepository.DeleteAsync(document.DocumentId);
_logger.LogInformation("Rolled back document {DocumentId} after ingestion failure.", document.DocumentId);
}
catch (Exception rollbackEx)
{
_logger.LogError(rollbackEx, "Failed to roll back document {DocumentId} after ingestion failure.", document.DocumentId);
}
TempData["Error"] = $"Failed to upload document '{title}' because ingestion could not be started: {ex.Message}";
return View();

Copilot uses AI. Check for mistakes.
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ingestion for document {DocumentId}", document.DocumentId);
TempData["Warning"] = $"Document '{title}' uploaded, but ingestion failed to start: {ex.Message}";
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, the warning message at line 171 exposes the full exception message from the ingestion failure. This could reveal internal system details to the user. Use a generic error message instead.

Copilot uses AI. Check for mistakes.
Subtype = subtype,
SeriesName = seriesName,
BookNumber = bookNumber,
Tags = "[]",
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Tags property is hardcoded to an empty JSON array "[]" when creating a document. This approach is inconsistent with how tags should be managed. If tags are not supported yet, consider making this property nullable or providing a proper initialization pattern. If tags are intended to be supported, the upload form should allow users to specify them.

Suggested change
Tags = "[]",
Tags = null,

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +92
<sl-card style="max-width: 700px; margin: 20px 0;">
<form asp-action="Upload" method="post" enctype="multipart/form-data" id="uploadForm">
@Html.AntiForgeryToken()

<div style="margin-bottom: 25px;">
<label for="title">
<strong>Document Title</strong>
<span style="color: red;">*</span>
</label>
<sl-input
id="title"
name="title"
placeholder="e.g., Emerald Eyes - Book 1"
required
style="margin-top: 5px;">
</sl-input>
</div>

<div style="margin-bottom: 25px;">
<label for="subtype">
<strong>Document Type</strong>
<span style="color: red;">*</span>
</label>
<sl-select
id="subtype"
name="subtype"
value="Book"
required
style="margin-top: 5px;">
<sl-option value="Book">Book</sl-option>
<sl-option value="Notes">Notes</sl-option>
<sl-option value="Outline">Outline</sl-option>
<sl-option value="Appendix">Appendix</sl-option>
</sl-select>
<sl-icon-button name="info-circle" label="Info" style="margin-left: 10px;"></sl-icon-button>
<div style="font-size: 13px; color: #666; margin-top: 5px;">
Choose the type that best describes this document
</div>
</div>

<div style="margin-bottom: 25px;">
<label for="seriesName">
<strong>Series Name</strong>
<span style="font-size: 13px; color: #666;">(optional)</span>
</label>
<sl-input
id="seriesName"
name="seriesName"
placeholder="e.g., The Evermore Chronicles"
style="margin-top: 5px;">
</sl-input>
</div>

<div style="margin-bottom: 25px;">
<label for="bookNumber">
<strong>Book Number</strong>
<span style="font-size: 13px; color: #666;">(optional)</span>
</label>
<sl-input
id="bookNumber"
name="bookNumber"
type="number"
min="1"
placeholder="e.g., 1"
style="margin-top: 5px;">
</sl-input>
</div>

<div style="margin-bottom: 25px;">
<label>
<strong>Upload File</strong>
<span style="color: red;">*</span>
</label>
<div style="font-size: 13px; color: #666; margin-top: 5px; margin-bottom: 10px;">
Supported formats: .txt, .md (max 10MB)
</div>

<div id="dropZone" style="
border: 2px dashed #cbd5e0;
border-radius: 8px;
padding: 40px;
text-align: center;
background: #f8fafc;
cursor: pointer;
transition: all 0.2s;
margin-top: 10px;">
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The views contain extensive inline styles throughout (e.g., lines 7-92 and beyond). This violates separation of concerns and makes the code harder to maintain. Consider extracting these styles into CSS classes in a separate stylesheet, which would improve maintainability, enable style reuse, and make it easier to implement consistent theming.

Copilot uses AI. Check for mistakes.
const submitBtn = document.getElementById('submitBtn');

// Click to browse
dropZone.addEventListener('click', () => fileInput.click());
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The click handler on line 160 will also trigger when clicking anywhere inside the drop zone, including when clicking on the file info display (which is a child of dropZone but hidden). Consider using a more specific event handler or checking if the file info is visible before triggering the file input click.

Suggested change
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('click', (event) => {
// Only trigger the file input when the file info section is not visible
const isFileInfoVisible = window.getComputedStyle(fileInfo).display !== 'none';
if (!isFileInfoVisible) {
fileInput.click();
}
});

Copilot uses AI. Check for mistakes.
JDRay42 and others added 2 commits December 24, 2025 13:52
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…tion, and error handling

Co-authored-by: JDRay42 <10964706+JDRay42@users.noreply.github.com>
@JDRay42
Copy link
Copy Markdown
Owner

JDRay42 commented Dec 24, 2025

@copilot apply changes based on the comments in this thread

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Dec 24, 2025

@copilot apply changes based on the comments in this thread

All actionable feedback from the review has been addressed in commit ca4c808:

✅ Keyboard accessibility for drop zone
✅ Alert timeout increased to 8 seconds
✅ Client-side file validation on drag-and-drop
✅ Form submission validation
✅ Checkbox label properly integrated into component
✅ Named constants for magic numbers
✅ Browser compatibility fallback for DataTransfer API
✅ Info button tooltip implemented
✅ Optimized segment sorting
✅ Document rollback on ingestion failure
✅ Sanitized error messages
✅ Drop zone click handler guards

The remaining suggestions (inline styles, Tags property) are architectural decisions consistent with the existing codebase patterns.

Copy link
Copy Markdown
Owner

@JDRay42 JDRay42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As requested.

@JDRay42 JDRay42 merged commit 558a42f into main Jan 6, 2026
2 checks passed
@JDRay42 JDRay42 deleted the copilot/create-document-upload-ui branch January 7, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants