38 KiB
bDS Rust Rewrite — Core Plan
Goal
Ship a native desktop Rust application (macOS primary, cross-platform ready) that fully replaces the current TypeScript app for the primary blogging workflow:
- open an existing project
- create and edit content
- preview content locally
- generate the site
- publish the site
Core is not a toy MVP. Core must already be a production-capable replacement for the current app's content pipeline.
Core Planning Rules
- Critical path first. Do not start advanced parity work while the authoring and publishing path is incomplete.
- Compatibility before optimization. Matching data and output behavior matters more than early performance work.
- Engine and UI land together at milestone boundaries. No long-lived backend-only branches of product functionality.
- No new persistence formats during core.
- Every milestone ends with a runnable app, not just passing unit tests.
Explicit Compatibility Contract
Must stay identical
- SQLite schema semantics and data readability
- post markdown files and frontmatter layout
- translation file naming and frontmatter layout
- media sidecars and thumbnail conventions
- media translation records
- post-link tracking records
- template file formats and lookup rules
- menu document read format for rendering
- generated routes, feeds, sitemaps, and local preview behavior
- metadata diff and rebuild-from-filesystem behavior
- full-text search behavior (SQLite FTS5 virtual tables for posts and media)
- slug generation behavior (verify
deunicodeoutput matches currenttransliterationoutput for the project's content corpus)
May change intentionally
- app implementation language and architecture
- editor technology
- scripting runtime: Python out, Lua in
- internal process model: no Electron main/renderer split
- UI framework: Iced replaces Electron/web UI
- platform integration: muda/rfd replace Electron's built-in menu and dialog APIs
Compatibility truth
User content and project data are compatible. User-authored Python scripts are not compatible and are treated as legacy artifacts.
Architecture Decisions
- Single process Rust app: no Electron, no IPC boundary.
- Iced (Elm architecture) for UI: Message-driven update cycle. All user interactions produce
Messagevariants; state mutations happen in a centralizedupdate()function; views are pure functions that return element trees. - muda for native menus: cross-platform native menu bar from day one. NSMenu on macOS, Win32 menus on Windows, GTK/dbus on Linux. Key equivalents and menu validation handled through muda's API.
- rfd for file dialogs: cross-platform native open/save/folder dialogs. NSOpenPanel/NSSavePanel on macOS, equivalents elsewhere.
- objc2 for macOS lifecycle shim (cfg-gated): thin bridge for
application:openFile:,application:openURLs:, and otherNSApplicationDelegatehooks that muda/rfd do not cover. ~50 lines of platform code. - Custom editor widget (bds-editor crate): syntax-highlighting markdown/Liquid/Lua editor built on ropey (rope buffer), syntect (syntax highlighting), and cosmic-text (font shaping and text layout). This is the highest-risk custom component and gets a proof-of-concept in Wave 0.
- rusqlite + embedded SQL migrations: no ORM. SQL stays explicit. Migrations managed via
refinery. - tokio as the async runtime: preview server (axum), SSH publishing, file watching, and rfd async dialogs all require an async executor. tokio is the standard choice and is used workspace-wide. Synchronous engine code in bds-core does not use tokio directly — it remains callable from both async (bds-ui) and sync (bds-cli) contexts.
- Plain-text markdown editor first: no WYSIWYG in core. Live preview is required. This is an intentional regression from the current app's Milkdown WYSIWYG editor. A rich editor can be added as an extension bucket after core ships — and the bds-editor widget provides a natural foundation for it.
- Lua for user-authored scripting:
mluawith Lua 5.4. Only user-authored macros, transforms, and utility scripts run through Lua. Built-in macros are native Rust — see the macro architecture section below. - One-shot AI operations are core scope: translation, image description / alt text generation, and title suggestion use a simple OpenAI-compatible HTTP client (
reqwest+serde_json). The endpoint is user-configurable (OpenAI, Anthropic-via-proxy, local Ollama, etc.). These are fire-and-forget request/response calls — no streaming, no tool use, no chat history. The AI chat UI, streaming responses, and tool execution remain in Extension Bucket C.
Iced Architecture Patterns
The Iced Elm architecture imposes a specific application structure:
Message routing
All user interactions, menu events, file dialog results, platform lifecycle events, and async task completions are expressed as variants of a root Message enum. The application's update() method matches on these variants and mutates state accordingly. This replaces the "command dispatcher" pattern seen in imperative UI frameworks.
View composition
Views are pure functions: fn view(&self) -> Element<Message>. They read application state and return a widget tree. Views never mutate state directly. This makes UI code inherently testable — you can assert on the element tree without rendering.
Command and subscription model
Side effects (file I/O, network, timers, platform events) are expressed as Command or Task values returned from update(). Menu events from muda arrive via a Subscription that polls MenuEvent::receiver(). Platform lifecycle events from the objc2 shim arrive via a similar channel-based subscription.
State model
The UI state must track at minimum:
- active project
- open tabs and selected tab
- selected entities
- dirty editors (with undo/redo state in bds-editor)
- task progress
ui_locale- project render settings including
content_language - menu state (enabled/disabled/checked items synced to app state)
Workspace Structure
bds-rust/
├── Cargo.toml
├── crates/
│ ├── bds-core/
│ │ ├── src/
│ │ │ ├── db/
│ │ │ ├── engine/
│ │ │ ├── model/
│ │ │ ├── render/
│ │ │ ├── scripting/
│ │ │ ├── i18n/
│ │ │ └── util/
│ ├── bds-editor/
│ │ ├── src/
│ │ │ ├── buffer.rs # ropey rope wrapper, edit operations
│ │ │ ├── highlight.rs # syntect integration, incremental rehighlight
│ │ │ ├── layout.rs # cosmic-text buffer/shaping/layout
│ │ │ ├── cursor.rs # cursor model, selection, multi-cursor
│ │ │ ├── history.rs # undo/redo operation log
│ │ │ ├── input.rs # key event → buffer mutation mapping
│ │ │ ├── widget.rs # Iced custom widget implementation
│ │ │ └── lib.rs
│ ├── bds-ui/
│ │ ├── src/
│ │ │ ├── app.rs # Iced Application impl, root Message enum, update()
│ │ │ ├── platform/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── macos.rs # objc2 lifecycle shim (cfg-gated)
│ │ │ │ └── menu.rs # muda menu construction and event routing
│ │ │ ├── views/
│ │ │ ├── components/
│ │ │ └── i18n/
│ └── bds-cli/
├── migrations/
├── fixtures/
│ ├── compatibility-projects/
│ └── golden-generated-sites/
└── docs/
└── scripting/
Core Feature Scope
Included in core
- project open/create/select
- posts and translations
- media import and metadata editing
- tags and categories
- template editing and validation
- settings and publishing preferences
- live preview and full static generation
- publish via SSH and rsync
- metadata diff and rebuild-from-filesystem
- native menus and shortcuts via muda (cross-platform)
- native file dialogs via rfd (cross-platform)
- macOS lifecycle hooks via objc2 shim (open-file, URL-open)
- syntax-highlighting editor for markdown, Liquid templates, and Lua scripts via bds-editor
- built-in macros implemented natively in Rust (gallery, youtube, vimeo, photo_archive, tag_cloud)
- Lua runtime for user-authored macros, transforms, and utility scripts
- generated Lua scripting API docs
- FTS5 virtual tables for in-app post and media search
- one-shot AI operations: translation, image description / alt text generation, title suggestion (configurable OpenAI-compatible endpoint via
reqwest)
Deferred to extensions
- AI chat UI, streaming responses, and tool execution (Bucket C)
- Git UI and history tools
- WordPress import wizard
- embeddings and duplicate detection
- translation validation reports
- menu editor UI
- documentation browser UI
- MCP and remote automation surfaces
- Blogmark transform service (external content capture pipeline)
- A2UI server-driven UI surfaces
- WYSIWYG / rich markdown editor (builds on bds-editor foundation)
Cross-Cutting Requirements
Native platform shell
Core includes:
- native menu bar via muda (App, File, Edit, View, Window, Help menus)
- standard key equivalents and accelerators via muda
- menu enable/disable validation synced to application state
- menu actions routed as
Messagevariants into the Iced update cycle - file-open and folder-open dialogs via rfd
- macOS: open-file from Finder and URL scheme handling via objc2 lifecycle shim
- proper window lifecycle and recent-project tracking
Split localization
Two independent localization domains are required:
ui_locale: detected from the OS and used for menus, dialogs, toasts, and workspace chromecontent_language: read from project settings and used for rendering, preview, feeds, sitemaps, and generation
No design in core may collapse those into a single "current language" field.
Editor widget requirements
The bds-editor crate is a custom Iced widget that provides syntax-highlighting text editing. It must support:
- Buffer: ropey
Ropefor O(log n) edits and line indexing - Syntax highlighting: syntect
HighlightLineswith incremental rehighlighting on edits. Grammars needed: Markdown, Liquid (HTML), Lua, YAML (frontmatter), JSON - Text layout: cosmic-text
Bufferfor font shaping, glyph positioning, and line layout - Cursor: position tracking, selection (shift+movement), click-to-place, click-and-drag selection
- Input handling: standard key bindings (arrows, home/end, page up/down, word movement with option/ctrl), text insertion, delete/backspace, tab/indent
- Undo/redo: operation log tracking edit groups, triggered by standard shortcuts
- Line numbers: gutter with line numbers, synchronized scroll
- Scroll: vertical and horizontal scrolling, viewport-aware rendering (only lay out visible lines)
- Soft wrap: configurable per editor instance
- IME support: proper handling of composition events from winit for CJK and other input methods — test early
The widget emits Iced Message variants for content changes, cursor movement, and save requests. The parent view owns the buffer state and passes it to the widget.
Metadata parity matrix
Before any engine work begins, create a compatibility inventory for every persisted field across:
- database columns
- markdown frontmatter
- translation frontmatter
- media sidecars
- project metadata files
- template files
- generated output metadata
For each field record:
- source of truth
- persisted locations
- read path
- write path
- rebuild behavior
- metadata diff behavior
- publish behavior
This matrix is a release artifact, not a temporary note.
Liquid template feature subset
The current default templates use a small subset of the Liquid specification. Before implementing the Liquid render pipeline, inventory the exact features used. The current templates (12 files: 3 main templates, 5 macro templates, 4 partials in src/main/engine/templates/) use only:
- Tags:
if/elsif/else,for,assign,render(partials), whitespace stripping ({%- -%}) - Filters (built-in):
default,escape,url_encode,append,size - Filters (custom, must be re-implemented in Rust):
i18n(translation lookup by key and language),markdown(markdown-to-HTML with macro expansion, URL rewriting, and media path canonicalization) - Not used:
unless,case/when,capture,layout,include,comment,raw,date,truncate,split,join,where,group_by,map,sort,reverse,cycle,tablerow,increment/decrement, and most other standard filters
This means the Rust Liquid implementation only needs roughly 35% of the full specification. Use liquid-rust or a minimal custom engine scoped to just the features above. User templates may use additional features, but parity with the current default templates is the release gate.
Built-in macro architecture
The current app has two distinct macro systems that must not be conflated:
- Built-in macros (
gallery,youtube,vimeo,photo_archive,tag_cloud) — implemented in JavaScript, rendered server-side during generation. Each has a corresponding.liquidtemplate insrc/main/engine/templates/macros/. These are not Python scripts and never were. - User-authored script macros — implemented in Python (Pyodide), invoked via the
PythonMacroWorkerRuntimeworker thread. These are replaced by Lua in the Rust app.
In the Rust app:
- Built-in macros become native Rust functions in
bds-core/render. They are part of Wave 4 (rendering parity), not Wave 6 (Lua scripting). Rendering cannot produce correct output without them. - User-authored macros run through the Lua runtime (
mlua). This is Wave 6 scope. During Wave 4, unknown macro placeholders should produce a visible "unsupported macro" marker rather than silently dropping content.
Macro invocation syntax in content: [[macro_name param1="value1" param2="value2"]]
Slug generation compatibility
The current app uses the transliteration npm package for Unicode-to-ASCII conversion in slugs. The plan specifies deunicode for Rust. These libraries may produce different output for edge cases (CJK characters, Cyrillic, accented Latin, etc.). Add a slug compatibility test suite to M0 fixtures that runs both libraries against the project's actual content corpus and documents any divergences.
Client-side search index (Pagefind)
If the current TypeScript app generates a Pagefind search index as part of site generation, the Rust app must produce the same artifact. Pagefind publishes a Rust library (pagefind crate) with a programmatic API (pagefind::api::PagefindIndex). The generation pipeline feeds rendered HTML to PagefindIndex::add_html_file() and writes the resulting index files via PagefindIndex::get_files(). No external binary, no npm — this is a pure Rust library dependency, fully compatible with the no-JavaScript constraint.
Determine during the compatibility inventory (M0) whether Pagefind or another client-side search solution is used. If Pagefind: add it to the generation pipeline in Wave 4 via the pagefind crate. If a different solution: document it and plan accordingly.
Image processing
Media import requires thumbnail generation and potentially format conversion (e.g., to WEBP). The current TypeScript app uses sharp (libvips bindings). The Rust equivalent is the image crate for decoding/encoding and basic transforms (resize, crop). If WEBP encoding performance or advanced processing (EXIF handling, ICC profiles) proves insufficient with image alone, libvips Rust bindings (libvips-rs) are the fallback. Start with image — it covers the common cases and has no system dependencies.
Planned Crate Registry
All crate choices for core scope, organized by subsystem. This prevents ad-hoc technology decisions during implementation.
Foundation (Wave 0 onward)
| Crate | Purpose | Notes |
|---|---|---|
rusqlite (bundled, vtab) |
SQLite database access | Bundled compiles SQLite from source, vtab enables FTS5 |
refinery |
SQL migration management | Replaces hand-rolled migration loader |
uuid (v4) |
Entity identifiers | |
serde + serde_json |
Serialization/deserialization | Used everywhere |
serde_yaml |
YAML frontmatter parsing/writing | Posts, translations, media sidecars |
chrono |
Date/time handling | |
sha2 |
Content hashing, checksums | |
deunicode |
Unicode-to-ASCII for slug generation | Verify against transliteration output |
thiserror |
Typed error definitions in library crates | bds-core, bds-editor |
anyhow |
Ergonomic error handling in application crates | bds-ui, bds-cli |
tokio |
Async runtime | Preview server, publish, file watching, async dialogs |
UI Framework (Wave 0 onward)
| Crate | Purpose | Notes |
|---|---|---|
iced (wgpu, advanced, image) |
Application framework | Elm architecture, GPU-accelerated via wgpu/Metal |
muda |
Cross-platform native menu bar | NSMenu / Win32 / GTK |
rfd |
Cross-platform native file dialogs | NSOpenPanel / Win32 / GTK |
Editor Widget (Wave 0 onward, bds-editor crate)
| Crate | Purpose | Notes |
|---|---|---|
ropey |
Rope buffer for text storage | O(log n) edits, line indexing |
syntect |
Syntax highlighting | Sublime Text grammars: Markdown, Liquid, Lua, YAML, JSON |
cosmic-text |
Font shaping, text layout | Same engine as cosmic-DE |
Platform Lifecycle (Wave 0 onward, macOS cfg-gated)
| Crate | Purpose | Notes |
|---|---|---|
objc2 |
Objective-C runtime bindings | |
objc2-foundation |
Foundation framework types | |
objc2-app-kit |
AppKit framework (NSApplicationDelegate hooks) | ~50 lines of shim code |
Data Layer (Wave 1)
| Crate | Purpose | Notes |
|---|---|---|
serde_yaml |
Frontmatter read/write | Already listed in foundation |
notify |
Filesystem watching | Detect external file changes affecting open editors |
image |
Thumbnail generation, format conversion | Start here; libvips-rs as fallback if needed |
walkdir |
Recursive directory traversal | Rebuild-from-filesystem, media import |
Rendering and Generation (Wave 4)
| Crate | Purpose | Notes |
|---|---|---|
pulldown-cmark |
Markdown → HTML | Fast, CommonMark-compliant |
liquid |
Liquid template rendering | Scoped to the ~35% feature subset actually used |
quick-xml |
RSS/Atom feeds, sitemaps | Also handles OPML menu documents |
rayon |
Parallel site generation | Parallelize page rendering across CPU cores |
axum |
Preview HTTP server | Lightweight, tokio-based |
Publishing (Wave 5)
| Crate | Purpose | Notes |
|---|---|---|
ssh2 |
SSH/SCP file upload | For publish-via-SSH targets |
| (shell out) | rsync invocation | rsync ships with macOS/Linux; invoke as child process |
Client-Side Search (Wave 4)
| Crate | Purpose | Notes |
|---|---|---|
pagefind |
Search index generation | Rust library API: PagefindIndex::add_html_file() + get_files(). No CLI binary needed. |
AI — One-Shot Operations (Wave 4–5)
| Crate | Purpose | Notes |
|---|---|---|
reqwest |
HTTP client for AI endpoints | OpenAI-compatible Chat Completions API. Used for translation, alt text, title suggestion. |
Scripting (Wave 6)
| Crate | Purpose | Notes |
|---|---|---|
mlua (lua54) |
Lua 5.4 embedding | User-authored macros, transforms, utility scripts. Use vendored feature to compile Lua from source. |
Extension-Only Crates (not used in core)
| Crate | Purpose | Bucket |
|---|---|---|
git2 |
Git operations | A (Git + Validation) |
ort |
ONNX inference for embeddings | D (Embeddings) |
usearch |
HNSW vector index | D (Embeddings) |
Critical Path
The hard sequence is:
- compatibility inventory and fixtures
- exact-read and exact-write data layer
- native platform shell and command system (muda menus, rfd dialogs, Iced message routing)
- editor widget MVP (ropey + syntect + cosmic-text proof of concept)
- editors for posts, media, templates, scripts, and settings
- preview and generation parity
- one-shot AI operations (translation, alt text, title suggestion)
- publish and integrity tooling
- Lua built-ins plus generated script API docs
Anything outside that path is noise until the previous step is stable.
Core Milestones
Milestone M0: Compatibility Baseline
- fixture projects checked in
- golden harness working
- schema readable
- native empty app launches with Iced window and muda menu bar
- bds-editor proof-of-concept renders a markdown file with syntax highlighting
Milestone M1: Data Fidelity
- posts, translations, media, tags, and settings round-trip
- rebuild works
- metadata diff works
- no format drift on fixture writes
Milestone M2: Native Workspace
- projects open in a real app window
- native menus and shortcuts work via muda
- sidebar, tabs, and message routing work
- file dialogs work via rfd
Milestone M3: Authoring
- post, translation, media, template, and script editing works using bds-editor
- errors surface in the UI
- all required authoring entities are reachable from the workspace
Milestone M4: Rendering Parity
- preview is trustworthy
- generation matches golden output
- route, feed, and sitemap parity is acceptable
- one-shot AI operations work (translation, image alt text, title suggestion) with a configurable OpenAI-compatible endpoint
Milestone M5: Operate And Ship
- publish works end to end
- integrity checks surface actionable issues
- Lua built-ins and scripting docs are complete enough for users
Core release happens only after M5.
Wave 0: Foundation And Compatibility Inventory
Goal: Bootstrapped workspace, exact compatibility target defined, golden fixtures checked in, and editor widget risk retired.
Deliverables
- Cargo workspace for
bds-core,bds-editor,bds-ui,bds-cli - SQLite connection layer, migrations loader, and base models
- compatibility inventory document covering all persisted fields (including
mediaTranslations,postLinks, and FTS5 virtual tables) - Liquid feature inventory documenting exactly which tags, filters, and patterns the default templates use
- slug compatibility test suite comparing
deunicodeoutput againsttransliterationoutput for fixture content - fixture projects copied from the current app
- golden-file harness for file writes and generation output comparisons
- empty native app window: Iced window with muda-driven menu bar (App/File/Edit/View/Window/Help)
- bds-editor proof-of-concept: custom Iced widget rendering a markdown file with syntax highlighting via ropey + syntect + cosmic-text. Must demonstrate: text display with highlighting, cursor placement, text insertion, basic scrolling. This retires the highest-risk component early.
- Iced architecture patterns document covering: message design conventions, subscription model for menu events and platform hooks, custom widget patterns for bds-editor
Dependencies
# Core
rusqlite = { version = "0.33", features = ["bundled", "vtab"] }
refinery = { version = "0.8", features = ["rusqlite"] }
uuid = { version = "1", features = ["v4"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
chrono = "0.4"
sha2 = "0.10"
deunicode = "1"
thiserror = "2"
anyhow = "1"
tokio = { version = "1", features = ["full"] }
walkdir = "2"
# UI framework
iced = { version = "0.13", features = ["wgpu", "advanced", "image"] }
# Editor widget
cosmic-text = "0.12"
ropey = "1"
syntect = "5"
# Platform integration (cross-platform)
muda = "0.15"
rfd = "0.15"
# Platform lifecycle (macOS only)
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-foundation = "0.3"
objc2-app-kit = "0.3"
Not all of these are needed in Wave 0 — this is the full foundation set. Wave-specific additions (image, pulldown-cmark, liquid, axum, rayon, quick-xml, pagefind, reqwest, ssh2, mlua, notify) are added when their wave begins. See the Planned Crate Registry for the complete list.
Tests
- open real bDS databases and verify read access across all tables (including
mediaTranslations,postLinks, FTS5 tables, and AI/catalog tables that must not cause errors even though they are not used in core) - verify empty Rust app opens as a native window with working menu bar
- verify bds-editor PoC renders highlighted text and accepts keyboard input
- verify fixture loader and golden harness run in CI
- slug compatibility tests pass for all fixture content
Done when
- workspace builds on macOS (and ideally on Linux for CI verification)
- fixture projects load
- compatibility corpus exists
- Liquid feature inventory is complete
- bds-editor PoC works
- Iced patterns doc is available
- native app shell with menus exists
- milestone M0 acceptance review passes
Wave 1: Core Data Layer And Filesystem Contract
Goal: Exact-read and exact-write support for the current project data model.
Key crates introduced
serde_yamlfor frontmatter parsing/writingimagefor thumbnail generation and format conversion during media importwalkdirfor recursive directory traversal during rebuild-from-filesystemnotifyfor filesystem change detection (may be deferred to Wave 5 if not needed until publish)
Engines
ProjectEnginePostEngineMediaEnginePostMediaEngineTagEngineMetaEngineTaskManagerMetadataDiffEngineRebuildEngineor equivalent rebuild services
File and DB responsibilities
- draft vs published post lifecycle
- translation files with canonical linkage
- media sidecars and thumbnails (thumbnail generation via
imagecrate) - tag/category sync
- checksum and content-hash tracking
- rebuild database from filesystem
- metadata diff against filesystem
Required behavior
- published posts continue storing body content in files, not in DB
- every field persisted by the current app remains persisted in the Rust app in the same places
- metadata writes, metadata diff, rebuild, and publish all use the same field mapping
Tests
- round-trip tests for posts, translations, media, templates, and project metadata
- rebuild tests using fixture projects
- metadata diff tests proving no false negatives for known field changes
- file-by-file golden comparisons against TypeScript output
Done when
- Rust can read and write existing project data without format drift
- rebuild restores the DB from files accurately
- metadata diff catches every field covered by the matrix
- milestone M1 acceptance review passes
Wave 2: Native Platform Workspace Shell
Goal: Native-feeling desktop application shell with real menus and command plumbing.
UI scope
- workspace window (Iced application)
- activity bar
- sidebar
- tab bar
- status bar
- task/output panel
- project selector
- message routing layer (root
Messageenum with command dispatch inupdate())
Platform scope
- App, File, Edit, View, Window, Help menus via muda
- accelerators and key equivalents via muda
- menu enable/disable synced to app state (e.g., Save disabled when no dirty editor)
- menu events received via
SubscriptionpollingMenuEvent::receiver() - file/folder dialogs via rfd
- macOS: open-file from Finder and URL-open handled by objc2 lifecycle shim, forwarded as
Messagevariants - recent project tracking (app-managed list; no platform API dependency)
State model
The Iced application state must track at minimum:
- active project
- open tabs
- selected entities
- dirty editors
- task progress
ui_locale- project render settings including
content_language
Tests
- tab lifecycle
- message dispatch from both menu events and keyboard entry points
- app-shell integration tests for opening a fixture project
- rfd dialog invocation tests (mocked where needed)
Done when
- the app can be navigated by menu or keyboard alone
- native menu labels are localized from the UI locale
- shell behavior feels like a native desktop app
- milestone M2 acceptance review passes
Wave 3: Content Editing UI
Goal: Full editing UI for the content types required by the publishing workflow.
Views
- dashboard
- post editor (using bds-editor with markdown + YAML frontmatter highlighting)
- translation editor (using bds-editor)
- media browser and media editor
- tags view
- settings view
- templates view and template editor (using bds-editor with Liquid/HTML highlighting)
- scripts view and script editor (using bds-editor with Lua highlighting)
- lightweight linked-media and post-links views where needed for parity
Required capabilities
- create, edit, duplicate, publish, unpublish, discard, and delete posts
- edit title, slug, excerpt, tags, categories, language, author, template assignment
- import media (via rfd file dialog) and edit title, alt, caption, author, tags, language
- create and edit templates with syntax validation
- create and edit Lua scripts with syntax validation
- expose errors and conflicts through dialogs and task panel output
- undo/redo in all editor instances via bds-editor history
Editor widget maturation
By Wave 3, bds-editor must support the full feature set documented in the editor widget requirements section:
- all cursor movement patterns (arrows, word, line, page, home/end)
- selection (shift+movement, click-and-drag, double-click word select)
- copy/cut/paste via system clipboard
- undo/redo with edit grouping
- line numbers gutter
- incremental syntax rehighlighting on edits
- IME input for non-Latin scripts
- configurable soft wrap
Tests
- integration tests from sidebar selection to persisted save
- template edit and validation tests
- script save and validation tests
- editor widget tests for cursor movement, selection, undo/redo, and IME composition
Done when
- every content type needed by preview/generation/publish has an editor
- template management is reachable and usable from the UI
- scripts are manageable from the UI even if advanced tooling is deferred
- bds-editor handles real-world editing scenarios reliably
- milestone M3 acceptance review passes
Wave 4: Rendering, Preview, And Generation
Goal: Reproduce the current site's rendering pipeline and local preview behavior.
Engines
TemplateEnginePageRendererBlogGenerationEnginePreviewServer(axum, localhost-only HTTP server)SearchIndexEngineif required for parity of generated sites
Key crates introduced
pulldown-cmarkfor Markdown → HTMLliquidfor Liquid template rendering (scoped to the feature subset)quick-xmlfor RSS/Atom feeds, sitemaps, and OPML menu document readingrayonfor parallel page rendering during generationaxumfor the preview HTTP server (runs on tokio)pagefindfor client-side search index generation (Rust library API, not CLI binary)reqwestfor one-shot AI operations (translation, alt text, title suggestion) against an OpenAI-compatible endpoint
Rendering pipeline
- load published posts, translations, menus, templates, and project settings
- resolve templates using the same lookup rules as the current app
- render markdown to HTML via
pulldown-cmark - expand built-in macros (gallery, youtube, vimeo, photo_archive, tag_cloud) natively in Rust — these do not go through the Lua runtime
- for user-authored Lua macros: delegate to the Lua runtime if available, otherwise emit an "unsupported macro" placeholder
- apply Liquid templates with custom
i18nandmarkdownfilters - rewrite URLs and media references
- generate archives, feeds (via
quick-xml), sitemaps, and search artifacts required for parity - feed rendered HTML pages to Pagefind via
pagefind::api::PagefindIndex::add_html_file(), then write the search index files viaget_files()— if client-side search index is required for parity - write only changed outputs when safe to do so (track output file content hashes in a
generatedFileHashestable, matching the current app's skip-unchanged-writes behavior) - parallelize page rendering via
rayonwhere safe (each page render is independent)
Preview rules
- preview binds only to localhost
- preview and generated HTML use local assets only
- preview must support drafts and language-prefixed routes
- rendered language is controlled by project settings, not by UI locale
One-shot AI operations
Wave 4 introduces the one-shot AI client in bds-core. This is a minimal reqwest-based HTTP client that sends single request/response calls to an OpenAI-compatible Chat Completions endpoint. No streaming, no tool use, no chat history.
Operations:
- Translation: translate a post or media metadata field to a target language. Used from the translation editor and media editor.
- Image description / alt text: generate an alt text description for a media item. Used from the media editor.
- Title suggestion: suggest a title from post content. Used from the post editor.
Configuration:
- endpoint URL (default: none — AI features are opt-in)
- API key (stored securely, not in project files)
- model name (configurable per operation or globally)
Constraints:
- AI operations are entirely optional. The app is fully functional without any AI endpoint configured.
- When no endpoint is configured, AI-related UI elements are disabled or hidden.
- Error responses produce user-visible feedback (toast or inline error), never silent failures.
- Request/response payloads use the OpenAI Chat Completions wire format. This works with OpenAI, Anthropic-via-proxy, local Ollama, or any compatible endpoint.
Tests
- fixture-based generation comparisons against current app output
- preview route tests for posts, drafts, assets, media, and language routes
- template compatibility tests using current
.liquidtemplates
Done when
- the same project can be generated by both apps with matching output
- preview is accurate enough to be trusted as an authoring tool
- milestone M4 acceptance review passes
Wave 5: Publishing And Operational Integrity
Goal: Publishing and integrity tooling needed to operate the app in production.
Key crates introduced
ssh2for SSH/SCP file upload- rsync invoked as child process (ships with macOS/Linux)
Engines and services
PublishEngineSiteValidationDiffServiceor equivalent core validation serviceNotificationWatcherif needed for filesystem coherence in core workflows
Required capabilities
- upload generated site via SCP or rsync
- upload media and thumbnails correctly
- exclude metadata-only files from deploy targets where appropriate
- surface publish progress and failures in the UI
- detect and surface external file changes that affect open editors or preview accuracy
Tests
- publish tests against mocked remote targets
- validation tests on generated sites
- watcher tests for externally modified content files
Done when
- publishing works end to end from the Rust app
- operator-visible integrity issues are surfaced before or during publish
- milestone M5 operational review is unblocked pending Wave 6 completion
Wave 6: Lua Runtime And Scripting Docs
Goal: Deliver user-authored scripting via Lua and complete scripting documentation.
Built-in macros (gallery, youtube, vimeo, photo_archive, tag_cloud) are already implemented as native Rust in Wave 4. Wave 6 covers only the user-extensible scripting layer.
Engines
ScriptEngineLuaRuntimeLuaApi
Required scope
- user-authored Lua macros invoked at render time via
[[macro_name]]syntax - user-authored transforms (post processing pipelines)
- user-authored utility scripts
- Lua API bridge exposing post, media, tag, and project data to scripts
Documentation requirements
- generated Lua API documentation from source annotations or schema definitions
- canonical data structure reference for script-visible types
- examples for macro, transform, and utility scripts
- docs bundled with the app and exported as markdown files in
docs/scripting/
Tests
- Lua execution tests
- API bridge tests
- macro compatibility tests for built-in macros
- docs sync tests proving generated docs match exposed API
Done when
- Lua scripting covers the built-ins needed by current templates and render flows
- scripting docs are complete enough for third-party script authors
- milestone M5 acceptance review passes
Core Dependency Graph
Wave 0 Foundation + Editor PoC
↓
Wave 1 Data + Filesystem Contract
↓
Wave 2 Native Platform Shell (Iced + muda + rfd)
↓
Wave 3 Content Editing UI (bds-editor maturation)
↓
Wave 4 Rendering + Preview + Generation
↓
Wave 5 Publishing + Operational Integrity
↓
Wave 6 Lua Runtime + Scripting Docs
Wave 4 depends on built-in macros (native Rust, not Lua). Wave 6 can start earlier for Lua runtime bootstrapping, but core release is not complete until user-authored scripting and docs are done.
Core Release Checklist
Core ships only when all of the following are true:
- An existing bDS project opens without manual migration.
- Posts, translations, media, templates, and settings can be edited in-app.
- Preview and generation match the current app closely enough to pass golden tests.
- Publishing works against supported remote targets.
- Metadata diff and rebuild are accurate.
- The app is a native desktop app with native menus and shortcuts.
- Lua replaces Python and has proper generated API documentation.
Cross-Platform Notes
The core stack (Iced + muda + rfd) is cross-platform. The only platform-specific code is the macOS lifecycle shim in bds-ui/src/platform/macos.rs. To ship on Linux or Windows:
- Linux: no lifecycle shim needed (file open arrives as CLI args; URL handling via XDG). Iced, muda, and rfd work natively.
- Windows: similar lifecycle shim for file associations and URL protocol handling via Windows APIs. Iced, muda, and rfd work natively.
Cross-platform packaging is not core scope, but the architecture does not accumulate macOS-only technical debt that would block it.