The app schema: one JSON description renders To-do & Reminders with native components (#478)
* web+ios+mac: the app schema — one JSON description renders To-do & Reminders native A declarative, human/LLM-readable JSON schema (docs/app-schema.md) now formally describes how a mini-app looks and behaves — state shape, event folds, grace-period partition, actions (emit/schedule/unschedule/ parse), a searchable sectioned list view, and a detail "show" view. Two interpreters consume the same schema: - web/static/apps/schema-engine.js (pure, DOM-free; tested in Node) - ios/LChatCore SchemaEngine.swift + SchemaAppView.swift (SwiftUI) Tapping To-do or Reminders on iOS now opens a native screen (schema Interface + companion Chat via the native feed, toggled in the header); the Mac's companion split and takeover panes render the same native interface in place of the app webview. Other apps keep their webviews. The contract is pinned three ways in npm test: the schema fold replays the shared event vectors, fuzzed parity proves it identical to the hand-written todo.js/remind.js reducers, and the native schema/vector copies must be byte-identical to the canonical files. SchemaEngineTests runs the same vectors and semantics on the Swift side. Native transport reuses the existing wire: REST POST /api/messages for events, /api/schedule for reminder fires (scheduled-first, sid recorded, offline fallback ticker), and it seeds from the encrypted "apps" snapshot before folding newer events, so long lists survive the message window. The server keeps seeing only ciphertext. No changelog entry yet — native-facing; it ships with the next iOS/Mac release per the release-timing rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web+mac: grace rings drain clockwise (todo, remind, schema renderer) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac+ios: HIG design pass on the schema renderer — grouped lists, native search, empty states, destructive confirms Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac: quiet focused search field + inline empty state (drop emptySymbol from the schema) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web+mac+ios: Reminders-style completion flourish — check fills, a beat, then tuck (no countdown ring) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * merge fixes: dedupe threadState + decryptData/encryptData twins added on both sides Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * apps: optimistic emit + composer spinner, native NSSearchField, drag-to-reorder todos (todo.move) - Native emits fold before the POST (ordered send queue; a failed send hints inline and reconciles by reloading) — adding a todo is instant; the composer shows a small spinner for actions with a real pre-fold round-trip (Reminders' schedule step). - The Mac search field is the platform's NSSearchField — no more hand-drawn capsule flashing the white field-editor background on focus. - New move fold op + section reorder surface in BOTH engines, todo.move event + move action in the schema, moveItemAfter in apps.js for the hand-written todo.js twin, native onMove drag with visual→array anchor mapping, vectors + mirrored JS/Swift tests, docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ios+mac: group threads render shared schema apps native SchemaAppStore/cache take an explicit appId (a group hosts several shared apps on one thread); the Mac group pane and the iOS picked-app surface swap in SchemaAppView when the picked app has a schema, webview embed otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * web: todo & remind render through the schema engine (schema-render.js) One schema now drives BOTH platforms end to end: the app files wire schemaEvents/schemaRender into defineApp and keep only glue (commands, preview/unread, chat wording, the reminder parser). The DOM renderer consumes the same listModel/rowModel SwiftUI does — search and drag-to-reorder arrive on the web for free; the fold-up tuck beat and per-thread transient UI state carry over. Docs + README updated; fuzz parity repurposed as the wiring net. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ios+mac: composer keeps focus after adding (burst entry, Return chains) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> ---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
31 files changed,
+4940
−699
CLAUDE.md+1 −1changelog/2026-07-09-ring-clockwise.md+1 −0docs/app-schema.md+355 −0ios/LChat/Views/NativeChatScreen.swift+16 −2ios/LChat/Views/SchemaAppScreen.swift+55 −0ios/LChat/Views/ThreadListView.swift+9 −4ios/LChatCore/Package.swift+4 −1ios/LChatCore/Sources/LChatCore/AppSchemaRegistry.swift+28 −0ios/LChatCore/Sources/LChatCore/Crypto.swift+12 −14ios/LChatCore/Sources/LChatCore/LChatAPI.swift+0 −1ios/LChatCore/Sources/LChatCore/SchemaAppStore.swift+329 −0ios/LChatCore/Sources/LChatCore/SchemaAppView.swift+573 −0ios/LChatCore/Sources/LChatCore/SchemaEngine.swift+738 −0ios/LChatCore/Sources/LChatCore/Schemas/remind.json+124 −0ios/LChatCore/Sources/LChatCore/Schemas/todo.json+123 −0ios/LChatCore/Tests/LChatCoreTests/Fixtures/schema-vectors-remind.json+156 −0ios/LChatCore/Tests/LChatCoreTests/Fixtures/schema-vectors-todo.json+208 −0ios/LChatCore/Tests/LChatCoreTests/SchemaEngineTests.swift+334 −0ios/LChatMac/NativeChatView.swift+40 −15package.json+1 −1test/apps.test.mjs+1 −1test/event-vectors/todo.json+69 −1test/schema-engine.test.mjs+389 −0web/static/apps.js+18 −0web/static/apps/README.md+11 −0web/static/apps/remind.js+31 −326web/static/apps/schema-engine.js+522 −0web/static/apps/schema-render.js+521 −0web/static/apps/schemas/remind.json+124 −0web/static/apps/schemas/todo.json+123 −0web/static/apps/todo.js+24 −332
CLAUDE.mdLoading diff…
changelog/2026-07-09-ring-clockwise.mdLoading diff…
docs/app-schema.mdLoading diff…
ios/LChat/Views/NativeChatScreen.swiftLoading diff…
ios/LChat/Views/SchemaAppScreen.swiftLoading diff…
ios/LChat/Views/ThreadListView.swiftLoading diff…
ios/LChatCore/Package.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/AppSchemaRegistry.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/Crypto.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/LChatAPI.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/SchemaAppStore.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/SchemaAppView.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/SchemaEngine.swiftLoading diff…
ios/LChatCore/Sources/LChatCore/Schemas/remind.jsonLoading diff…
ios/LChatCore/Sources/LChatCore/Schemas/todo.jsonLoading diff…
ios/LChatCore/Tests/LChatCoreTests/Fixtures/schema-vectors-remind.jsonLoading diff…
ios/LChatCore/Tests/LChatCoreTests/Fixtures/schema-vectors-todo.jsonLoading diff…
ios/LChatCore/Tests/LChatCoreTests/SchemaEngineTests.swiftLoading diff…
ios/LChatMac/NativeChatView.swiftLoading diff…
package.jsonLoading diff…
test/apps.test.mjsLoading diff…
test/event-vectors/todo.jsonLoading diff…
test/schema-engine.test.mjsLoading diff…
web/static/apps.jsLoading diff…
web/static/apps/README.mdLoading diff…
web/static/apps/remind.jsLoading diff…
web/static/apps/schema-engine.jsLoading diff…
web/static/apps/schema-render.jsLoading diff…
web/static/apps/schemas/remind.jsonLoading diff…
web/static/apps/schemas/todo.jsonLoading diff…
web/static/apps/todo.jsLoading diff…