You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The sandbox currently offers Import / Export buttons inside both the Markdown panel and the Config panel, but they produce versioned JSON envelopes (e.g. { type: 'postext-markdown', version: 1, markdown }) rather than the files users actually expect. There is also no way to save or load a whole project (config + markdown + custom resources + fonts) in one step.
This issue reworks the import/export story into two clearly separated tiers:
Panel-level import/export → plain files (.md for markdown)
Project-level save/load → a single .postext bundle
Current behavior: exportMarkdownToJson(state.markdown) downloads a JSON file wrapping the markdown string. importMarkdownFromJson(file) only accepts that envelope.
Target behavior:
Export downloads the current markdown as a plain .md file (no JSON envelope, no metadata, no referenced resources embedded).
Import accepts a .md / .markdown / .txt file and replaces the editor content with its raw text.
Default filename on export should derive from doc.metadata.title when present (slugified), otherwise document.md.
Reject files that don't look like text (binary sniff), with a clear error toast.
The versioned JSON helpers for markdown (exportMarkdownToJson, importMarkdownFromJson) can be removed — the bundle format (§3) is the canonical carrier for structured state.
2. Config panel — keep JSON, but out of scope here
The Config panel's JSON import/export is fine as-is and is not part of this issue. It stays useful for sharing only the styling of a document.
Add two icon buttons to the activity bar, above the theme toggle slot:
Save project (download icon) → downloads <title>.postext
Load project (upload icon) → opens a file picker for .postext
Both live at the activity-bar level (not inside a single panel) because the operation affects the entire sandbox state.
Bundle format
A .postext file is a single container (zip-based, e.g. via a small wrapper over fflate or the existing dependency tree — pick whichever is already in node_modules) with a predictable layout:
project.postext (zip)
├── manifest.json # { kind: 'postext-project', version: 1, createdAt, appVersion }
├── document.md # raw markdown
├── config.json # full PostextConfig (NOT stripped — explicit snapshot)
├── fonts/
│ ├── <fontId>.<ext> # binaries for each custom font variant
│ └── index.json # mapping: fontId → { family, weight, style, format }
└── resources/
└── <resourceId>.<ext> # referenced images or other binary resources
Rationale:
Zip so the bundle round-trips binaries without base64 bloat.
Manifest carries version + app metadata so future format changes can migrate.
Config stored in full (not stripped against defaults) to guarantee the project renders identically years later, even if defaults shift.
Fonts and resources indexed by id so ids embedded in the config remain stable across save/load cycles.
The bundle must include every custom font registered via the issue #44 work and every referenced resource in the document.
Save flow
Read current state: markdown, config, font binaries from IndexedDB, resources from IndexedDB.
Build the zip in-memory.
Trigger a browser download named <slugified-title>.postext (fall back to project.postext).
Load flow
User picks a .postext file.
Confirmation modal first — the load is destructive. Use ConfirmPopover with a clear message: "Loading this project will replace all current markdown, configuration, custom fonts, and resources in this sandbox. This cannot be undone. Continue?"
On confirm: parse the zip, validate the manifest, then atomically replace:
IndexedDB: custom font binaries + index, resource binaries
SandboxContext dispatches that refresh markdown + config + any derived state
On validation failure: show an error toast naming the problem (bad manifest, unsupported version, missing required entry); do not touch existing state.
State reset semantics
The load is all-or-nothing. If the user has unsaved work, the confirmation step is their last chance to cancel. We deliberately do not attempt a "merge" strategy.
Overview
The sandbox currently offers Import / Export buttons inside both the Markdown panel and the Config panel, but they produce versioned JSON envelopes (e.g.
{ type: 'postext-markdown', version: 1, markdown }) rather than the files users actually expect. There is also no way to save or load a whole project (config + markdown + custom resources + fonts) in one step.This issue reworks the import/export story into two clearly separated tiers:
.mdfor markdown).postextbundle1. Markdown panel — plain
.mdfilesLocation:
packages/postext-sandbox/src/sidebar/MarkdownPanel.tsx(buttons at lines 68–101),packages/postext-sandbox/src/storage/persistence.tsCurrent behavior:
exportMarkdownToJson(state.markdown)downloads a JSON file wrapping the markdown string.importMarkdownFromJson(file)only accepts that envelope.Target behavior:
.mdfile (no JSON envelope, no metadata, no referenced resources embedded)..md/.markdown/.txtfile and replaces the editor content with its raw text.doc.metadata.titlewhen present (slugified), otherwisedocument.md.The versioned JSON helpers for markdown (
exportMarkdownToJson,importMarkdownFromJson) can be removed — the bundle format (§3) is the canonical carrier for structured state.2. Config panel — keep JSON, but out of scope here
The Config panel's JSON import/export is fine as-is and is not part of this issue. It stays useful for sharing only the styling of a document.
3. Project-level save / load —
.postextbundleUI placement
Location:
packages/postext-sandbox/src/sidebar/ActivityBar.tsx(lines 180–192),apps/web/src/components/sandbox/SandboxPage/index.tsxAdd two icon buttons to the activity bar, above the theme toggle slot:
<title>.postext.postextBoth live at the activity-bar level (not inside a single panel) because the operation affects the entire sandbox state.
Bundle format
A
.postextfile is a single container (zip-based, e.g. via a small wrapper overfflateor the existing dependency tree — pick whichever is already innode_modules) with a predictable layout:Rationale:
The bundle must include every custom font registered via the issue #44 work and every referenced resource in the document.
Save flow
<slugified-title>.postext(fall back toproject.postext).Load flow
.postextfile.ConfirmPopoverwith a clear message: "Loading this project will replace all current markdown, configuration, custom fonts, and resources in this sandbox. This cannot be undone. Continue?"postext-sandbox-markdown,postext-sandbox-configSandboxContextdispatches that refresh markdown + config + any derived stateState reset semantics
The load is all-or-nothing. If the user has unsaved work, the confirmation step is their last chance to cancel. We deliberately do not attempt a "merge" strategy.
4. Warnings & error surfaces
Location:
packages/postext-sandbox/src/warnings/compute.tsmanifest.versionis older than the current one, run any registered migrations and surface a one-time info warning.missingFontwarning (from #44).resources/, emit amissingResourcewarning.Acceptance criteria
.mdfile named from the document title.md/.markdown/.txtfile and replaces editor content.postextbundle format implemented (zip withmanifest.json,document.md,config.json,fonts/,resources/)docs/configuration-en.mdxor a new section, and-esequivalents) describing the project bundle and the simplified markdown import/exportOut of scope
Related code
packages/postext-sandbox/src/sidebar/MarkdownPanel.tsx(lines 68–101)packages/postext-sandbox/src/sidebar/ConfigPanel.tsx(lines 79–112)packages/postext-sandbox/src/storage/persistence.tspackages/postext-sandbox/src/sidebar/ActivityBar.tsxapps/web/src/components/sandbox/SandboxPage/index.tsxpackages/postext-sandbox/src/context/SandboxContext.tsxpackages/postext-sandbox/src/panels/ConfirmPopover.tsxRelated issues