Skip to content

IIIF import direct#406

Merged
cubap merged 13 commits intomainfrom
iiif-import-direct
Feb 2, 2026
Merged

IIIF import direct#406
cubap merged 13 commits intomainfrom
iiif-import-direct

Conversation

@cubap
Copy link
Member

@cubap cubap commented Jan 28, 2026

interface for directly creating projects through querystring.

  • It should work if you are logged in or not.
  • It should turn multiple manifest= into multiple projects with safe failing
  • It should present the results for action once finished

It doesn't currently...

  • extend existing projects
  • allow for any pre-configuration of these projects
  • combine multiple manifests in a single project
  • relate these projects to each other as a collection

This contains

  • a component
  • an interface and permalink for app.t-pen.org (/import-manifest)
  • some simple documentation for its use

cubap added 2 commits January 28, 2026 13:27
Introduce a new Manifest Import feature: adds a web component (components/manifest-import/index.js) that imports multiple IIIF manifests into TPEN, handles authentication, creates projects sequentially, and renders progress/results. Includes docs and support pages (IMPLEMENTATION.md, README.md, QUICKSTART.md), an examples page and interface (interfaces/manifest-import/index.html, examples.html, INDEX.md), and interface metadata (manifest.yml). Uses TPEN Services POST /project/import?createFrom=URL and integrates with TPEN auth/token handling.
@cubap cubap changed the title Iiif import direct IIIF import direct Jan 28, 2026
@cubap cubap requested a review from Copilot January 28, 2026 19:41
@github-actions
Copy link
Contributor

github-actions bot commented Jan 28, 2026

Copy link
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 introduces a new "IIIF import direct" feature that enables users to create TPEN projects from multiple IIIF manifests via query string parameters. The feature includes a web component, interface page, comprehensive documentation, and examples.

Changes:

  • Added tpen-manifest-import web component that handles manifest parsing, authentication, batch project creation, and results display
  • Created /import-manifest interface page with responsive styling
  • Provided extensive documentation including quickstart guide, examples, and implementation details

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
components/manifest-import/index.js Main web component implementing the import workflow with authentication, API calls, and UI rendering
components/manifest-import/README.md Technical documentation for the component
components/manifest-import/IMPLEMENTATION.md Detailed implementation summary and architecture notes
interfaces/manifest-import/index.html Interface page hosting the component with permalink configuration
interfaces/manifest-import/examples.html Interactive examples and integration guide for developers
interfaces/manifest-import/QUICKSTART.md Quick start guide for end users
interfaces/manifest-import/INDEX.md Complete reference documentation with links to all resources
interfaces/manifest-import/manifest.yml Interface metadata and configuration

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

Visit this example with real manifests:

```
/import-manifest?manifest=http%3A%2F%2Fwww.e-codices.unifr.ch%2Fmetadata%2Fiiif%2Fsl-0002%2Fmanifest.json
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The URL in the example shows "/?manifest=" but should be "/import-manifest?manifest=" to match the permalink configured in index.html.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 86 to 231
```
/?manifest=http%3A%2F%2Fwww.e-codices.unifr.ch%2Fmetadata%2Fiiif%2Fsl-0002%2Fmanifest.json
```

### Multiple Manifests
```
/?manifest=URL1&manifest=URL2&manifest=URL3
```

**Key Points**:
- Manifests must be URL-encoded
- Multiple manifests use repeated `manifest` parameters
- Duplicates are automatically filtered out
- One manifest per parameter

## Component Architecture

### Private Properties
- `#manifests` - Array of manifest URLs to import
- `#createdProjects` - Array of created project objects
- `#isCreating` - Loading state flag
- `#authToken` - Current auth token

### Public Methods
- `load()` - Main entry point, orchestrates the workflow

### Private Methods
- `#extractManifests()` - Parses query parameters
- `#createProjects()` - Creates projects sequentially
- `#importManifest(url)` - API call for single manifest
- `render*()` - Various render methods for different states

### Render States
1. **renderNeedAuth()** - Login prompt
2. **renderNoManifests()** - No manifests found in URL
3. **renderCreating()** - Loading indicator
4. **renderResults()** - Results page with projects and errors

## API Integration

The component uses this TPEN Services API endpoint:

```
POST /project/import?createFrom=URL
Headers:
Authorization: Bearer {token}
Content-Type: application/json
Body:
{ "url": "https://example.com/manifest.json" }
```

**Response** (success):
```json
{
"_id": "project-id",
"label": "Project Name",
"layers": [...],
"metadata": [...],
"creator": "user-id",
...
}
```

## Error Handling

The component gracefully handles:

| Scenario | Behavior |
|----------|----------|
| Not authenticated | Shows login button |
| No manifests in URL | Shows helpful error with example |
| Invalid manifest URL | Listed in failed section with error |
| Network error | Caught and displayed |
| Invalid JSON response | Error message shown |
| Partial failures | Successful projects still display |

## Styling Features

- **Responsive Grid**: Project cards use CSS Grid that adapts to screen size
- **Mobile Optimized**: Single column on mobile, multi-column on desktop
- **Accessibility**: Proper contrast, readable fonts, clear hierarchy
- **Theme Support**: CSS custom properties for easy customization
- **Loading State**: Animated spinner with status text
- **Error States**: Clear visual distinction with colors and icons

## Results Display

### Successful Projects Show:
- Project title
- Project ID
- Layer and page counts
- "View" button (goes to project details)
- "Transcribe" button (starts transcription interface)

### Failed Imports Show:
- Error message
- Manifest URL (for reference)
- Grouped in separate "Failed" section

### Navigation:
- "Back to TPEN" button (links to home)
- "View Projects" button (if any succeeded)

## Key Design Decisions

1. **Web Component**: Encapsulated, reusable, no dependencies on external frameworks
2. **Sequential Creation**: Projects created one at a time to prevent overload
3. **No State Persistence**: Each session is independent
4. **Auth Delegation**: Leverages existing TPEN authentication system
5. **Graceful Degradation**: Works without JavaScript (prompts to enable)
6. **Duplicate Filtering**: Uses `Set` to remove duplicate manifest URLs

## Browser Compatibility

- ✓ Chrome 90+
- ✓ Firefox 88+
- ✓ Safari 14+
- ✓ Edge 90+
- ✓ Mobile browsers (iOS Safari, Chrome Android)

## Testing Scenarios

To test the feature, try these URLs:

```
# Single manifest (e-codices)
/?manifest=http%3A%2F%2Fwww.e-codices.unifr.ch%2Fmetadata%2Fiiif%2Fsl-0002%2Fmanifest.json

# Multiple manifests
/?manifest=URL1&manifest=URL2

# With duplicates (one will be filtered)
/?manifest=URL1&manifest=URL1&manifest=URL2

# No manifests (shows error)
/?manifest=
```

## Files Created

```
components/manifest-import/
├── index.js (576 lines - main component)
└── README.md (comprehensive documentation)

interfaces/manifest-import/
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The URL examples use "/?manifest=" but should use "/import-manifest?manifest=" to match the permalink configured in index.html. This appears in multiple places (lines 86, 90, 212, 221, 227, 231).

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines 575 to 579
#escapeHtml(text) {
const div = document.createElement('div')
div.textContent = text
return div.innerHTML
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The component defines its own #escapeHtml method, but there's a shared escapeHtml utility function at '/js/utils.js' that's used by other components (navigation-manager, project-options, quicktype-tool, etc.). For consistency with the codebase, import and use the shared utility instead of defining a private method. This also ensures the same escaping logic is applied across all components.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

</style>
</head>
<body>
<tpen-manifest-import></tpen-manifest-import>
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The custom element 'tpen-manifest-import' doesn't have any ARIA attributes or semantic markup. For better accessibility, consider adding role="main" or wrapping it in a main element so screen readers can properly identify the main content area.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +251 to +263
.loader-wrapper {
text-align: center;
}

.spinner {
border: 4px solid var(--light-gray, #f5f5f5);
border-top: 4px solid var(--interface-primary, #005a8c);
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The spinner animation in the loading state has no accessible label or ARIA attributes. Screen reader users won't know that content is loading. Add role="status" and aria-live="polite" to the loader-wrapper div, and consider adding aria-label="Loading" to the spinner div.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #407, to work on those changes. Once the pull request is ready, I'll request review from you.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #408, to work on those changes. Once the pull request is ready, I'll request review from you.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #409, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #410, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #411, to work on those changes. Once the pull request is ready, I'll request review from you.

cubap and others added 6 commits January 29, 2026 10:16
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Initial plan

* Fix URL format in INDEX.md to match permalink

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
* Initial plan

* Fix URL examples to use /import-manifest path

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
* Initial plan

* Replace custom #escapeHtml with shared utility from /js/utils.js

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
* Initial plan

* Fix /?manifest= to /import-manifest?manifest= across all documentation

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
* Initial plan

* Add main element wrapper for improved accessibility

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
</style>
</head>
<body>
<main>
Copy link
Member Author

Choose a reason for hiding this comment

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

@copilot put this inside of a tpen-page component instead of building out a bespoke interface

Copy link
Contributor

Copilot AI commented Jan 29, 2026

@cubap I've opened a new pull request, #412, to work on those changes. Once the pull request is ready, I'll request review from you.

@thehabes
Copy link
Member

Static Review Comments

@cubap - Here's a code review of this PR.

Branch: iiif-import-direct
Review Date: 2026-01-30
Files Changed: 8

Summary

This PR adds a new manifest import feature that allows users to import multiple IIIF manifests as TPEN projects via a single URL. The implementation includes a web component (tpen-manifest-import), interface pages, and comprehensive documentation. The code follows the project's web component patterns and integrates properly with the existing authentication system.

Overall, the feature is well-structured and includes good error handling for most scenarios. However, there are security vulnerabilities that need to be addressed before deployment, along with some code quality improvements.

Category Issues Found
🔴 Critical 1
🟠 Major 3
🟡 Minor 4
🔵 Suggestions 3

Critical Issues 🔴

Issue 1: XSS Vulnerability - Unescaped User Input in Error Display

File: components/manifest-import/index.js:537-541
Category: Security - XSS

Problem:
Error messages and manifest URLs from failed imports are inserted directly into HTML without sanitization. The error.message comes from API responses and error.manifestUrl comes directly from user-controlled query parameters. An attacker could craft a malicious manifest URL that, when displayed in the error section, executes JavaScript.

Current Code:

${failed.map(error => `
    <div class="error-item">
        <strong>${error.message}</strong>
        <div class="error-url">${error.manifestUrl}</div>
    </div>
`).join('')}

Suggested Fix:

${failed.map(error => `
    <div class="error-item">
        <strong>${escapeHtml(error.message)}</strong>
        <div class="error-url">${escapeHtml(error.manifestUrl)}</div>
    </div>
`).join('')}

How to Verify:

  1. Navigate to /import-manifest?manifest=<script>alert('XSS')</script>
  2. Verify the script tags are displayed as text, not executed
  3. Test with various XSS payloads including event handlers: manifest=<img src=x onerror=alert(1)>

Major Issues 🟠

Issue 1: Unused Import

File: components/manifest-import/index.js:8
Category: Dead Code

Problem:
The getUserFromToken function is imported but never used in the component. This adds unnecessary bytes and may confuse future maintainers.

Current Code:

import { getUserFromToken } from '../iiif-tools/index.js'

Suggested Fix:

// Remove this line entirely - the import is not used

How to Verify:

  1. Search the file for getUserFromToken - it only appears in the import statement
  2. Run the application and verify functionality is unchanged after removal

Issue 2: Error Handling - JSON Parse May Fail

File: components/manifest-import/index.js:91-93
Category: Error Handling

Problem:
When the API returns a non-OK response, the code attempts to parse JSON from the response. If the server returns a non-JSON error (e.g., HTML error page, 502 Bad Gateway), this will throw an exception that masks the original HTTP error.

Current Code:

if (!response.ok) {
    const errorData = await response.json()
    throw new Error(errorData.message || `HTTP ${response.status}`)
}

Suggested Fix:

if (!response.ok) {
    let errorMessage = `HTTP ${response.status}`
    try {
        const errorData = await response.json()
        errorMessage = errorData.message || errorMessage
    } catch {
        // Response was not JSON - use HTTP status
    }
    throw new Error(errorMessage)
}

How to Verify:

  1. Mock an API response that returns HTML (like a 502 gateway error page)
  2. Verify the error displays gracefully with the HTTP status code
  3. Verify JSON errors still show their message properly

Issue 3: Missing Component Cleanup

File: components/manifest-import/index.js
Category: Memory Leak

Problem:
The component attaches event listeners (e.g., login button click handler at line 156) but does not implement disconnectedCallback() to clean them up. If this component is removed and re-added to the DOM multiple times, it could lead to memory leaks.

Note: Since event listeners are attached to elements inside the shadow DOM and the innerHTML is replaced on each render, this is a lower-severity memory leak risk. However, adding disconnectedCallback() is good practice for the component lifecycle.


Minor Issues 🟡

Issue 1: Inconsistent Import Paths

File: components/manifest-import/index.js:7-9
Category: Code Consistency

Problem:
The component uses relative paths for some imports and absolute paths for others. This inconsistency could cause issues with different build configurations or when moving files.

Current Code:

import TPEN from '../../api/TPEN.js'
import { getUserFromToken } from '../iiif-tools/index.js'
import { escapeHtml } from '/js/utils.js'

Suggested Fix:
Use consistent import style and remove unused import:

import TPEN from '../../api/TPEN.js'
import { escapeHtml } from '/js/utils.js'

Issue 2: Accessibility - Missing ARIA Attributes on Loading State

File: components/manifest-import/index.js:291-297
Category: Accessibility

Problem:
The loading spinner has no ARIA attributes to communicate the loading state to screen reader users.

Suggested Fix:

<div class="container" role="status" aria-live="polite" aria-busy="true">
    <div class="loader-wrapper">
        <div class="spinner" aria-hidden="true"></div>
        <h2>Creating Projects</h2>
        <p>Importing ${this.#manifests.length} manifest${this.#manifests.length !== 1 ? 's' : ''}...</p>
    </div>
</div>

Issue 3: Accessibility - Missing Focus Styles

File: components/manifest-import/index.js:133-146
Category: Accessibility

Problem:
The button styles don't include a visible focus indicator for keyboard users. The default browser focus ring may be removed by the border: none style in some browsers.

Suggested Fix:

button:focus {
    outline: 2px solid var(--focus-ring, #005a8c);
    outline-offset: 2px;
}

button:focus:not(:focus-visible) {
    outline: none;
}

Issue 4: Documentation Date

File: components/manifest-import/IMPLEMENTATION.md:275-276
Category: Documentation

The documentation states specific creation dates. Consider if these will become stale or if they should be removed.


Suggestions 🔵

Suggestion 1: Consider Using Custom Dialog Instead of window.confirm()

File: components/manifest-import/index.js:44-45

The component uses window.confirm() for batch import confirmation. Consider using the existing TPEN GUI components for a more consistent user experience.


Suggestion 2: Add Rate Limiting Warning for Large Batches

File: components/manifest-import/index.js:43-52

For very large batch imports (e.g., >10 manifests), consider warning users about potential rate limiting or long wait times.


Suggestion 3: Add Visual Progress for Batch Imports

File: components/manifest-import/index.js:250-298

For batch imports, the loading screen shows a static message. Consider showing which manifest is currently being processed (e.g., "Creating project 3 of 5...").


Files Reviewed

  • components/manifest-import/index.js - Main component - needs security fix
  • components/manifest-import/README.md - Documentation - OK
  • components/manifest-import/IMPLEMENTATION.md - Implementation docs - OK
  • interfaces/manifest-import/index.html - Interface page - OK
  • interfaces/manifest-import/examples.html - Examples page - OK
  • interfaces/manifest-import/INDEX.md - Reference docs - OK
  • interfaces/manifest-import/QUICKSTART.md - Quick start guide - OK
  • interfaces/manifest-import/manifest.yml - Config - OK

Checklist for Developer

  • Address Critical XSS vulnerability - escape error.message and error.manifestUrl in renderResults()
  • Remove unused getUserFromToken import
  • Add try/catch around JSON parsing in error handling
  • Consider adding disconnectedCallback() for component cleanup
  • Add ARIA attributes for accessibility
  • Add focus styles for keyboard navigation
  • Run full test suite after changes
  • Self-review the fixes before requesting re-review

@thehabes thehabes self-requested a review January 30, 2026 17:21
Copy link
Member

@thehabes thehabes left a comment

Choose a reason for hiding this comment

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

Tested manually on a localhost deploy. Manifests are imported and projects are generated. I can start logged in or logged out.

See comments from the Claude Code /static-review they are all easy fixes and updates for the ones you don't want to ignore.

Follow up with copilot as well after making changes since it had so much to say earlier and you have done resolutions since.

@cubap
Copy link
Member Author

cubap commented Feb 2, 2026

@copilot Please follow up on Claude's recommendations:

  • [] Update from main to prevent merge conflicts
  • Address Critical XSS vulnerability - escape error.message and error.manifestUrl in renderResults()
  • Remove unused getUserFromToken import
  • Add try/catch around JSON parsing in error handling
  • Add disconnectedCallback() for component cleanup
  • Self-review the fixes before requesting re-review

Copy link
Contributor

Copilot AI commented Feb 2, 2026

@cubap I've opened a new pull request, #414, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits February 2, 2026 09:37
…omponent (#414)

* Initial plan

* Fix security and code quality issues in manifest-import component

Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cubap <1119165+cubap@users.noreply.github.com>
Add component lifecycle and concurrency improvements for manifest import: introduce a CleanupRegistry to manage event handlers and call cleanup.run() on disconnect; attach TPEN authentication and wait for tpen-authenticated events, render a need-auth state when not authorized, and guard initialization with a #hasInitialized flag to avoid double-init. Rework #createProjects to perform imports with a worker pool and a concurrency limit (5) while preserving result order and collecting per-manifest errors. Also enhance loader markup with ARIA attributes for better accessibility.
@cubap cubap merged commit 13a271f into main Feb 2, 2026
1 check passed
@cubap cubap deleted the iiif-import-direct branch February 2, 2026 16:19
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