Summary
Users should never see or copy blob: URLs inside eXeLearning editors or HTML source views.
blob: URLs are browser-temporary runtime URLs. They are not stable project references, they are not portable, and users can mistakenly copy them into their content thinking they are the real asset path.
The editor should expose only asset://... references to users and internally translate them to temporary blob: URLs only when needed for rendering or preview.
Problem
At the moment, users can end up seeing blob: URLs such as:
blob:https://static.exelearning.dev/62e7c455-f41c-45a5-a42a-9a8864d22cac
This is misleading because:
blob: URLs are temporary and browser/session-specific.
- They are not suitable for persistence in project content.
- Some users copy them manually into HTML.
- This can produce broken references, confusion, and hard-to-understand editing errors.
From a user perspective, the only meaningful reference should be something stable and project-aware, for example:
asset://8f3e9c2a-1b4d-4f7e-9a3c-2d1e5f7a9b3c.jpg
or, in a friendlier future form:
with the application internally resolving that to the current asset UUID and to a runtime blob: URL when rendering.
Current architecture context
The current codebase already seems aligned with this direction:
- The architecture documentation describes assets as using
asset://... URLs as the content-addressable reference format.
AssetManager also documents that assets are referenced with asset:// URLs in HTML.
- The same file clearly distinguishes those stored references from display-time blob URLs created with
URL.createObjectURL(...).
That means this issue is less about changing the storage model and more about enforcing a stricter boundary between:
- canonical persisted references →
asset://...
- temporary runtime rendering URLs →
blob:...
Proposal
Phase 1: low-risk fix
Ensure that users never see blob: URLs in editable content.
Concretely:
-
Do not write blob: URLs into the persisted HTML/Yjs content
- Before save / serialization, rewrite any known local blob reference back to its corresponding
asset://... URL.
- If a blob URL cannot be mapped to a known asset, show a validation warning and prevent saving that broken reference.
-
Do not show blob: URLs in source/code editing dialogs
- If the user opens HTML/source mode, the content shown should contain only
asset://... references.
-
Do not insert blob: URLs when using media/file pickers
- Insert
asset://... directly into the editor model.
- Convert to
blob: only in the rendering layer.
-
Add a defensive cleanup step on paste/import/save
- Detect
blob: references in HTML.
- If they match a locally known asset, replace them automatically with
asset://....
- If not, notify the user that temporary browser URLs are not valid project references.
Phase 2: optional usability improvement
Introduce a friendlier alias format for display, while keeping the UUID-based model internally:
Possible behavior:
- UI/source editor displays
asset://folder/image.jpg
- internally the model still resolves that to the canonical asset UUID
- export/runtime resolution continues using the current asset metadata
This would be more understandable for non-technical users than a raw UUID.
Suggested implementation approach
A simple implementation path could be:
A. Enforce canonicalization in one place
Create a single utility responsible for normalizing asset references in HTML:
- input: raw HTML from editor/source/import/paste
- output: normalized HTML with only
asset://... references
This utility could:
- replace known
blob: URLs using the existing reverse blob cache / asset metadata
- preserve valid
asset://... references
- ignore
http:, https:, data:, anchors, etc.
- optionally report unknown
blob: references
This avoids scattering the logic across multiple editors.
B. Apply normalization at the boundary points
Use that normalization in these moments:
- after inserting media from the file manager
- before writing HTML into Yjs / project state
- before opening source editor views
- during import/paste sanitization
C. Keep runtime translation separate
Rendering layers can still resolve asset://... to blob: internally for preview, iframe rendering, or local display.
That preserves the current runtime behavior while making the persisted/user-facing format predictable.
Why this is a good fit for the current design
This proposal works well with the current architecture because the project already has:
- a dedicated asset metadata layer
- canonical
asset:// references documented as the intended format
- a separate blob resolution mechanism for display/runtime
So the change is mainly about enforcing the intended abstraction consistently in the editor UI.
Advantages
- Prevents users from copying temporary browser-only URLs
- Reduces confusion in HTML/source editing
- Makes saved content deterministic and portable
- Keeps project content independent from a specific browser session
- Makes debugging easier because persisted references are stable
- Fits the existing
asset:// architecture instead of introducing a new storage model
Trade-offs / possible drawbacks
- Existing code paths may already depend on
blob: being inserted in some editors, so a few UI integrations may need adjustment.
- A strict save-time validation may initially surface previously hidden bad content.
- A friendly path-based syntax such as
asset://folder/image.jpg is more readable, but it introduces ambiguity around rename/move operations unless it is clearly treated as a UI alias rather than the canonical stored identifier.
- Keeping UUID-based
asset://uuid.ext as the canonical stored form is safer for deduplication and rename/move independence.
Recommended scope for this issue
I would recommend implementing this issue in two steps:
-
Fix now
- never expose
blob: to users
- always persist
asset://...
- normalize
blob: back to asset://... before save
-
Evaluate later
- whether a friendlier
asset://path/file.ext alias should be introduced for UI/source editing
Acceptance criteria
-
Users do not see blob: URLs in HTML/source editing workflows.
-
Saving content never persists blob: references.
-
Media insertion writes asset://... references into the editor model.
-
Any detected blob: URL in editable HTML is either:
- automatically converted to the matching
asset://..., or
- rejected with a clear validation message if it cannot be resolved.
-
Preview/runtime rendering can still use blob: internally without exposing it to the user.
Possible places to review in the codebase
The relevant parts appear to be around:
public/app/yjs/AssetManager.js
- editor/workarea integration points that insert or serialize HTML
- preview/runtime asset resolution code
A good first step would be to identify every code path where URL.createObjectURL(...) or resolved blob URLs are injected into editable DOM/source content, and replace that with canonical asset://... insertion plus runtime-only resolution.
Final note
The main goal is not to remove blob: from the runtime, but to make it an internal implementation detail.
For users, the project should behave as if asset references are always stable, portable and understandable.
Summary
Users should never see or copy
blob:URLs inside eXeLearning editors or HTML source views.blob:URLs are browser-temporary runtime URLs. They are not stable project references, they are not portable, and users can mistakenly copy them into their content thinking they are the real asset path.The editor should expose only
asset://...references to users and internally translate them to temporaryblob:URLs only when needed for rendering or preview.Problem
At the moment, users can end up seeing
blob:URLs such as:This is misleading because:
blob:URLs are temporary and browser/session-specific.From a user perspective, the only meaningful reference should be something stable and project-aware, for example:
or, in a friendlier future form:
with the application internally resolving that to the current asset UUID and to a runtime
blob:URL when rendering.Current architecture context
The current codebase already seems aligned with this direction:
asset://...URLs as the content-addressable reference format.AssetManageralso documents that assets are referenced withasset://URLs in HTML.URL.createObjectURL(...).That means this issue is less about changing the storage model and more about enforcing a stricter boundary between:
asset://...blob:...Proposal
Phase 1: low-risk fix
Ensure that users never see
blob:URLs in editable content.Concretely:
Do not write
blob:URLs into the persisted HTML/Yjs contentasset://...URL.Do not show
blob:URLs in source/code editing dialogsasset://...references.Do not insert
blob:URLs when using media/file pickersasset://...directly into the editor model.blob:only in the rendering layer.Add a defensive cleanup step on paste/import/save
blob:references in HTML.asset://....Phase 2: optional usability improvement
Introduce a friendlier alias format for display, while keeping the UUID-based model internally:
Possible behavior:
asset://folder/image.jpgThis would be more understandable for non-technical users than a raw UUID.
Suggested implementation approach
A simple implementation path could be:
A. Enforce canonicalization in one place
Create a single utility responsible for normalizing asset references in HTML:
asset://...referencesThis utility could:
blob:URLs using the existing reverse blob cache / asset metadataasset://...referenceshttp:,https:,data:, anchors, etc.blob:referencesThis avoids scattering the logic across multiple editors.
B. Apply normalization at the boundary points
Use that normalization in these moments:
C. Keep runtime translation separate
Rendering layers can still resolve
asset://...toblob:internally for preview, iframe rendering, or local display.That preserves the current runtime behavior while making the persisted/user-facing format predictable.
Why this is a good fit for the current design
This proposal works well with the current architecture because the project already has:
asset://references documented as the intended formatSo the change is mainly about enforcing the intended abstraction consistently in the editor UI.
Advantages
asset://architecture instead of introducing a new storage modelTrade-offs / possible drawbacks
blob:being inserted in some editors, so a few UI integrations may need adjustment.asset://folder/image.jpgis more readable, but it introduces ambiguity around rename/move operations unless it is clearly treated as a UI alias rather than the canonical stored identifier.asset://uuid.extas the canonical stored form is safer for deduplication and rename/move independence.Recommended scope for this issue
I would recommend implementing this issue in two steps:
Fix now
blob:to usersasset://...blob:back toasset://...before saveEvaluate later
asset://path/file.extalias should be introduced for UI/source editingAcceptance criteria
Users do not see
blob:URLs in HTML/source editing workflows.Saving content never persists
blob:references.Media insertion writes
asset://...references into the editor model.Any detected
blob:URL in editable HTML is either:asset://..., orPreview/runtime rendering can still use
blob:internally without exposing it to the user.Possible places to review in the codebase
The relevant parts appear to be around:
public/app/yjs/AssetManager.jsA good first step would be to identify every code path where
URL.createObjectURL(...)or resolved blob URLs are injected into editable DOM/source content, and replace that with canonicalasset://...insertion plus runtime-only resolution.Final note
The main goal is not to remove
blob:from the runtime, but to make it an internal implementation detail.For users, the project should behave as if asset references are always stable, portable and understandable.