The phone joins the format — KanbanMobile MVP: shared storage verbatim over an iCloud container

A second product, not a second edition: dev.rzen.indie.KanbanMobile (iOS 26,
iPhone-only) compiles Kanban/Storage as source files, so a format change that
breaks the phone breaks this build the day it's made. Boards live in
iCloud.dev.rzen.indie.Kanban — named after the Mac bundle id so the Mac app can
adopt the container later without a migration; until then the folder is
"Lanework" in iCloud Drive and the Mac opens boards there through the open
panel.

EchoLedger grows #if os(macOS) gates around its three consumer surfaces
(verdicts/BoardDiff, harvest/HarvestedReceipt, comment retirement/CommentPath)
— the recording side BoardWriter stamps compiles on every platform, and the
gates are the seam a future phone verdict surface lands behind. AgentGuide
stays Mac-only.

The phone's watcher is NSMetadataQuery: BoardIndexStore (one query, package
UTI export makes a .kanban directory one item, equality-gated rescans,
download kicks per pass), BoardSession (materialization sweep before every
fail-fast walk, NSFileCoordinator brackets, perform{} = coordinated write then
awaited reload, ParseMemo threaded), CloudHome (off-main container resolution,
LANEWORK_LOCAL_ROOT DEBUG override for simulator work without an account).

Screens: Boards -> lanes -> cards -> card editor, value-routed by ItemID with
every screen re-reading the live snapshot; leading swipe moves a card via
confirmationDialog, trailing swipe sends it to .trash/; the editor commits
title through the Mac's canonical rename path and body through writeBody, with
drafts that survive reloads; attributes are the three typed style fields
(icon, iconColor, background) — labels is a reserved unknown-field key and
deliberately has no editor. Settings carries IndieBackup (backup root = the
container's Documents, restore rebuild = an index rescan, controller
constructed only once the home resolves).

Arbiter: KanbanMobile green for iOS Simulator, Kanban green for macOS, 3006
unit tests / 517 suites passed (PointerLatencyTests excluded — mid-rework
uncommitted in a parallel session).

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-07 22:49:36 -04:00
parent c67c1037f1
commit eac1c02a7d
22 changed files with 2599 additions and 1 deletions
+67
View File
@@ -3,6 +3,7 @@ options:
bundleIdPrefix: dev.rzen.indie
deploymentTarget:
macOS: "26.0"
iOS: "26.0"
xcodeVersion: "26.0"
defaultConfig: Debug
@@ -51,6 +52,15 @@ packages:
libgit2:
url: https://github.com/ibrahimcetin/libgit2.git
exactVersion: 1.9.2
# Local backup/restore for the phone app's Settings tab (indie-backup skill). `KanbanMobile`
# only — see that target's dependencies below; the Mac app has no Settings surface for this
# yet. Pinned to `from: "2.0.0"`, the tag the persistence-agnostic API (a single `backupRoot`
# plus a rebuild-cache-on-restore callback — here, `BoardIndexStore.refresh()`, no database in
# the loop) shipped under; 1.0.x resolves to the older iCloud-required, CloudKit-coupled
# surface this app doesn't want.
IndieBackup:
url: https://git.rzen.dev/rzen/indie-backup.git
from: "2.0.0"
settings:
base:
@@ -143,6 +153,63 @@ targets:
SWIFT_STRICT_CONCURRENCY: complete
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
# MARK: - Lanework for iPhone
#
# **The mobile app is a second product, not a second edition** (distinct bundle id, ruled
# 2026-08-07): `dev.rzen.indie.KanbanMobile`, its own App Store record, iPhone-only for the MVP
# (iPad joins by widening TARGETED_DEVICE_FAMILY, not by a new target). It compiles
# `Kanban/Storage` **verbatim — the same source files, not a copy and not a package** — so the
# two apps cannot drift on the format contract: a storage change that breaks the phone breaks
# this project's build, in this repo, on the day it's made.
#
# What it deliberately does NOT compile: LiveStore (FSEvents — macOS-only machinery; the phone's
# change signal is NSMetadataQuery over the ubiquity container), Git/History (no git support in
# the mobile MVP), Tier, and all of `Kanban/UI`. The mobile app's own sources live under
# `KanbanMobile/`.
#
# Boards live in the iCloud Drive container `iCloud.dev.rzen.indie.Kanban` — named after the
# *Mac* app's bundle id because container ids are not bundle ids, and that name is the one the
# Mac app can adopt later without migrating anyone's data (see KanbanMobile.entitlements).
KanbanMobile:
type: application
platform: iOS
sources:
- KanbanMobile
# The shared storage engine — frontmatter, loader, writer, ranks, integrity. Pure
# Foundation + Yams + swift-markdown; no AppKit anywhere in the directory, which is what
# makes this line possible. One exclusion: AgentGuide reaches up into LiveStore's
# EchoLedger (its bracketed-write seam), and the AGENTS.md guide it writes is a Mac
# feature — agents work where the Mac app runs.
- path: Kanban/Storage
excludes:
- AgentGuide.swift
# The writer layer's one upward dependency: BoardWriter stamps every write into the
# task-local receipt ledger so a store can tell its own echoes from foreign changes. The
# recording side is pure CryptoKit + Foundation + Synchronization and compiles here; the
# ledger's *consumer* surfaces — verdicts (BoardDiff), harvest (HarvestedReceipt), comment
# retirement (CommentPath) — are the Mac watcher/committer's vocabulary and sit behind
# `#if os(macOS)` gates inside the file. On the phone the "watcher" is the metadata-query
# observer; when it grows a verdict surface, those gates are the seam it lands behind.
- path: Kanban/LiveStore/EchoLedger.swift
dependencies:
- package: Yams
# BodyMarkup (Kanban/Storage) imports Markdown, so the dependency rides along even though
# the MVP has no preview surface yet.
- package: swift-markdown
product: Markdown
- package: IndieBackup
settings:
base:
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanMobile
MARKETING_VERSION: "1.0"
INFOPLIST_FILE: KanbanMobile/Info.plist
CODE_SIGN_ENTITLEMENTS: KanbanMobile/KanbanMobile.entitlements
GENERATE_INFOPLIST_FILE: false
SWIFT_STRICT_CONCURRENCY: complete
# iPhone only. The iPad milestone widens this to "1,2" rather than adding a target.
TARGETED_DEVICE_FAMILY: "1"
# MARK: - Unit tests
#
# The suite, hosted by the app: every test in `KanbanTests/` exercises the storage format, the