Skip to content

Have vault Fallback on Embedded Canvas Data in the Manifest#434

Closed
thehabes wants to merge 11 commits into417-strengthen-attempts-to-load-canvasfrom
429-use-embedded-canvas-data
Closed

Have vault Fallback on Embedded Canvas Data in the Manifest#434
thehabes wants to merge 11 commits into417-strengthen-attempts-to-load-canvasfrom
429-use-embedded-canvas-data

Conversation

@thehabes
Copy link
Copy Markdown
Member

@thehabes thehabes commented Feb 5, 2026

Closes #429

@thehabes thehabes self-assigned this Feb 5, 2026
@thehabes thehabes linked an issue Feb 5, 2026 that may be closed by this pull request
@thehabes
Copy link
Copy Markdown
Member Author

thehabes commented Feb 5, 2026

Consider IndexedDB for caching large IIIF resources

Currently, vault.js skips localStorage for manifests, collections, and annotation collections (SKIP_LOCAL_STORAGE_TYPES) because they can be megabytes and would blow the ~5MB localStorage quota. These large resources are only held in the in-memory Map, so they're lost on every page navigation and must be re-fetched.

In TPEN Interfaces, users frequently move between pages within a project, and the same manifest is needed repeatedly — that's unnecessary network overhead each time.

Suggestion: Use IndexedDB for persisting these large types across page loads. It's a native browser API (no npm packages needed), has much larger storage quotas (typically 50MB+), and handles structured data well.

The tradeoff is that IndexedDB is async-only with a more verbose API than localStorage.getItem/setItem. However, vault.get() is already async so reads fit naturally. For vault.set() (currently synchronous), the IndexedDB write could be fire-and-forget since the in-memory Map remains the primary lookup path.

This isn't blocking for the current PR, but worth considering as a follow-up improvement.

Comment thread api/TPEN.js Outdated
// Pre-load manifest into vault so embedded canvases are available for fallback
const manifestUri = ev.detail?.manifest?.[0]
if (manifestUri) {
import('../js/vault.js').then(m => m.default.get(manifestUri, 'manifest').catch(() => {}))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This couples vault.js into TPEN.js which I don't think is wanted or needed.

If this behavior is wanted, it can be added to vault.js so it looks and listens when the module is loaded. There is no reason to load vault and start populating it unless the component is going to use it.

Comment thread components/default-transcribe/index.js Outdated
let page
try {
page = vault.get({id:annotationPageID,type:"AnnotationPage"}) ?? await vault.load(annotationPageID)
page = await vault.get(annotationPageID, 'annotationpage', true, 'tpen-transcription')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the actual "Vault" syntax. We might look at supporting both, but I think we're good here.

Comment thread components/default-transcribe/index.js Outdated
return userMessage(err.message ?? err.statusText ?? err.text ?? 'Unknown error')
}
}
if (!page?.items) return userMessage('No annotations found on this page')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Page is an AnnotationPage here... we might need to look at what happens to an AnnotationList, since it has annotations in a different place and wouldn't have items.

Comment thread components/default-transcribe/index.js Outdated
})).then(results => results.flat())
this.#transcriptionContainer.append(...lines)
this.activeLine = lines[0].line
this.activeLine = lines[0]?.line
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This ? should never be used because !lines is an escape clause above.

Comment thread components/legacy-annotator/plain.js Outdated
}
const context = resolvedPage["@context"]
if(!(context.includes("iiif.io/api/presentation/3/context.json") || context.includes("w3.org/ns/anno.jsonld"))){
if(context && !(context.includes("iiif.io/api/presentation/3/context.json") || context.includes("w3.org/ns/anno.jsonld"))){
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like the context && syntax. If we think there will be something missing, we can context?.includes?.() which will not only trip if context is undefined, but also not true-fail on values like 5 which is truthy but has no "includes()".

In real life this line feels like a utility to be extracted, but if it's just used here, I'm over it.

Comment thread js/vault.js Outdated
*/
async _resolveCanvasFromManifest(canvasId, component) {
// 1. Check if already available from a cached manifest
let embedded = this._getEmbeddedCanvas(canvasId)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just vault.get(canvas)

Comment thread js/vault.js Outdated
* @param {string} canvasId - The canvas ID
* @param {string} manifestUri - The manifest URI containing this canvas
*/
registerManifestHint(canvasId, manifestUri) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Gross. I literally prefer to never care ever what Manifest it is part of. In fact, if it gets shunted into several and the same ID is used, I hope the most recent one loaded replaces the older and causes conflicts.

Comment thread js/vault.js Outdated
)
this._dispatchCanvasError(error)
} else {
return null
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why not return when response is not ok?

Comment thread js/vault.js Outdated
if (isCanvas) {
const validationError = this._validateCanvas(data, uri)
if (validationError) {
validationError.component = component
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this the only place component matters? Do we need this?

Comment thread js/vault.js
this.store.delete(type)
// Clean up canvasToManifest entries when clearing manifests
if (type === 'manifest') {
this.canvasToManifest.clear()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

boo

@cubap cubap mentioned this pull request Feb 9, 2026
@thehabes thehabes closed this Feb 9, 2026
@thehabes thehabes deleted the 429-use-embedded-canvas-data branch February 9, 2026 15:34
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.

Utility to Check For Embedded Canvas Data When Canvas URI Won't Resolve

2 participants