Skip to content

Project import/export: plain .md in the editor, full .postext bundle from the sidebar #47

@drnachio

Description

@drnachio

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:

  • Panel-level import/export → plain files (.md for markdown)
  • Project-level save/load → a single .postext bundle

1. Markdown panel — plain .md files

Location: packages/postext-sandbox/src/sidebar/MarkdownPanel.tsx (buttons at lines 68–101), packages/postext-sandbox/src/storage/persistence.ts

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.

3. Project-level save / load — .postext bundle

UI placement

Location: packages/postext-sandbox/src/sidebar/ActivityBar.tsx (lines 180–192), apps/web/src/components/sandbox/SandboxPage/index.tsx

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

  1. Read current state: markdown, config, font binaries from IndexedDB, resources from IndexedDB.
  2. Build the zip in-memory.
  3. Trigger a browser download named <slugified-title>.postext (fall back to project.postext).

Load flow

  1. User picks a .postext file.
  2. 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?"
  3. On confirm: parse the zip, validate the manifest, then atomically replace:
    • localStorage: postext-sandbox-markdown, postext-sandbox-config
    • IndexedDB: custom font binaries + index, resource binaries
    • SandboxContext dispatches that refresh markdown + config + any derived state
  4. 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.

4. Warnings & error surfaces

Location: packages/postext-sandbox/src/warnings/compute.ts

  • After load, if the bundle's manifest.version is older than the current one, run any registered migrations and surface a one-time info warning.
  • If the bundle references a font id that wasn't shipped inside the bundle (corrupted export), emit the existing missingFont warning (from #44).
  • If the bundle references a resource id not present in resources/, emit a missingResource warning.

Acceptance criteria

  • Markdown panel Export downloads a plain .md file named from the document title
  • Markdown panel Import accepts a plain .md / .markdown / .txt file and replaces editor content
  • Old versioned-JSON markdown import/export code removed
  • Activity bar has Save project and Load project icon buttons placed above the theme toggle
  • .postext bundle format implemented (zip with manifest.json, document.md, config.json, fonts/, resources/)
  • Save gathers markdown, full config, all custom fonts from IndexedDB, and all resources
  • Load shows a destructive-action confirmation before replacing state
  • Load atomically replaces markdown, config, fonts, and resources; failures leave existing state intact
  • Manifest version check with a clear error for unsupported versions
  • Info/error surfaces after load for version migrations and missing referenced assets
  • Docs updated (docs/configuration-en.mdx or a new section, and -es equivalents) describing the project bundle and the simplified markdown import/export

Out of scope

  • Cloud sync / server-side project storage — bundles are local files only.
  • Partial imports (e.g. "load only the config from this bundle") — use the existing Config panel JSON for that.
  • Bundle diffing / merging.
  • Auto-save of bundles.
  • Sharing bundles via URL.

Related code

Related issues

  • #44 — custom fonts (bundle must carry their binaries)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions