Skip to content

feat(playground): save & load decks from the filesystem#89

Open
tilucasoli wants to merge 4 commits into
mainfrom
tilucasoli/save-load-decks-from-the-filesystem-in-the-playg
Open

feat(playground): save & load decks from the filesystem#89
tilucasoli wants to merge 4 commits into
mainfrom
tilucasoli/save-load-decks-from-the-filesystem-in-the-playg

Conversation

@tilucasoli

Copy link
Copy Markdown
Collaborator

Closes #88.

Gives the playground editor a real .md file behind it, backed by continuous auto-save, so edits survive refresh/relaunch. A deck is now a file the user owns, living in ~/Documents/SuperDeck/. Desktop-only (macOS); the storage interface leaves room for a web impl later.

What's included

Storage layer (playground/lib/core/data/data_sources/) — mirrors the existing AssetCacheStore interface + conditional-import native-impl pattern:

  • DeckFileStore (_base/_io/_stub + conditional export): read/write/createDeck/exists/pickDeckFile/watch/decksDirectoryPath. Native impl uses dart:io + path_provider + file_picker; watching reuses core's FileWatcher. Web stub throws.
  • AppSettingsStore: persists the single last-opened path as JSON in the app-support dir (no new shared_preferences dependency).

Binding controllerfeatures/editor/domain/stores/deck_file_controller.dart (ChangeNotifier):

  • Debounced auto-save (~400ms); no manual save, no dirty flag.
  • File watching with self-write filtering via content hash (generateValueHash) so auto-save doesn't loop.
  • External wins → auto-reload when the bound file changes on disk.
  • Delete/move → unbind + warn, keeping in-memory content, never resurrecting the file.
  • new / open / initialize (reopen last file, fall back to a seeded default deck).

Editor integrationTextEditorController gains an onMarkdownChanged sink (from its existing single chokepoint); reloads drive the existing MarkdownEditor.replaceMarkdown port. New EditorBootstrap awaits initialize() before mounting, seeding the editor with the loaded content. Store/settings are injected via AppProviders so tests supply in-memory fakes.

UI — editor header (filename + New/Open + unbound warning banner), new-deck dialog with collision re-prompt, and ⌘N / ⌘O shortcuts.

Acceptance criteria

  • Editing auto-saves to the bound .md (debounced); no manual save action.
  • New (⌘N) prompts for a name, creates <name>.md, seeds a one-slide starter, rebinds; collisions re-prompt.
  • Open (⌘O) opens a .md from anywhere and rebinds.
  • Current filename shown in the editor header.
  • On launch, last-opened file reopened; falls back to a default on first run / if missing.
  • Bound file watched; the app's own auto-saves filtered from external-change detection.
  • External change auto-reloads into the editor.
  • Deleting/moving the bound file unbinds + warns without losing in-memory content or resurrecting the file.
  • File operations behind a storage interface with a native-only impl.

Impacted packages

packages/playground only. Adds path_provider + file_picker deps (and two test-only platform-interface deps). GeneratedPluginRegistrant.swift updated to register file_picker on macOS.

Verification

  • fvm flutter analyze — clean.
  • fvm flutter test114 tests pass, incl. 13 controller-logic tests (debounce, self-write filtering, external reload without looping, delete→unbind, new/open, collision) and 6 native-store I/O tests.
  • fvm flutter build macos --debug — succeeds.

Not yet done: a manual click-through of the flows in the running desktop app.

🤖 Generated with Claude Code

Give the playground editor a real .md file behind it, backed by
continuous auto-save, so edits survive refresh/relaunch.

- DeckFileStore: storage interface + native (dart:io + path_provider +
  file_picker) impl and web stub, via conditional export (mirrors the
  AssetCacheStore pattern). AppSettingsStore persists the last-opened path.
- DeckFileController: debounced auto-save, file watching with self-write
  filtering (content hash), external-wins auto-reload, delete/move ->
  unbind + warn (keeps in-memory content), and new/open/launch flows
  (reopen last file, fall back to a seeded default in ~/Documents/SuperDeck/).
- Editor integration: TextEditorController gains an onMarkdownChanged sink;
  reloads drive the existing MarkdownEditor.replaceMarkdown port. New
  EditorBootstrap awaits initialize() and seeds the editor with the file.
- UI: editor header (filename + New/Open + unbound warning), new-deck dialog
  with collision re-prompt, and Cmd+N / Cmd+O shortcuts.
- Store/settings are injected via AppProviders so tests use in-memory fakes.

Desktop-only; the storage interface leaves room for a web impl later.

Tests: DeckFileController logic + native store I/O. Closes #88.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 5205063):

https://superdeck-dev--pr89-tilucasoli-save-load-7n0gtvwj.web.app

(expires Fri, 07 Aug 2026 13:41:51 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: bd68fc230762285849207e7e120aaf87cd4ca2f9

tilucasoli and others added 3 commits July 8, 2026 10:04
- Cancel the pending debounce before the unchanged-content early return so a
  revert-to-saved within the debounce window no longer writes stale content.
- Replace the 8-char hash self-write filter with a direct content comparison,
  removing a collision path that could drop a genuine external edit and the
  full-document hash recomputed on every keystroke.
- Advance the synced marker only after a successful write, so a failed
  auto-save no longer desyncs the self-write filter.
- Guard pickDeckFile against an empty/odd picker result instead of letting
  `.single` throw a StateError that escapes openDeck's catch.
- Add the user-selected files entitlement so the sandboxed macOS app can open
  the native file picker.
- Add regression tests for the revert-in-debounce and failed-write cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New/Open are both ghost buttons, grouped and left-aligned in the bar.
- The filename bar now sits on top of the TextEditor pane instead of
  spanning the full width above the sidebars.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the filename + New/Open bar into the TextEditor's HeroCard, above
the SuperEditor, so it sits directly on top of the editing surface. The
bar keeps the filename on the left and the ghost New/Open actions on the
right. Drop the now-unused header from EditorPage and its resize-handle
offsets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Save & load decks from the filesystem in the playground editor

1 participant