Space in Finder opens the board — a Quick Look preview extension that outlines a .kanban package

A board is a folder, and a folder previews as a folder. `KanbanQuickLook.appex` gives it a
document's preview instead: the board's name, its tint and its symbol, then its lanes in display
order with each one's card count and its first few card titles.

The reading is `BoardOutline` (Kanban/Storage), deliberately not `BoardLoader.load`. The loader
throws on a half-broken board — right for opening one, wrong for pressing Space, where the honest
answer is the part that reads; it visits every card and lists `attachments/` and counts `comments/`
inside each; and it carries trash, tombstone migration and the defect stream, none of which renders.
This walk never throws and is capped at every level (`BoardOutlineLimits`): 12 lanes shown of at
most 100 considered, 6 card titles per lane of at most 200 parsed, counts by readdir-plus-stat up to
2000 per lane and never a parse. It re-derives nothing that decides *what* the answer is —
`FrontmatterDocument` parses, `IntegrityRules.isIdentityShaped` says what a lane or a card is,
`BoardLoader.directoryCandidates` supplies the stray tolerance, `Ranks` supplies display order,
`Palette` resolves colours — only *how far to look*.

The reply is HTML, the one data-based reply that reflows: a Quick Look panel is resized by the
user and a board outline is a wrapping row of columns, so a drawing block baked at a fixed
`contentSize` would be the wrong size a moment later. It gets vector text, its own scrolling and
light/dark for free. The board tint is a wash under the title and a lane's edge accent — never
under text, because a preview has none of `ContrastMath`'s ink-picking machinery and should not
grow one.

The extension compiles `Kanban/Storage` whole, the `KanbanMobile` arrangement — the directory is
one unit in practice, so a narrower list is not on offer. `STORAGE_ONLY` is new: EchoLedger's
consumer sections speak the live store's vocabulary, and the phone's `#if os(macOS)` cannot exclude
them from a target that *is* macOS. Platform, and layer. Nothing else defines it.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-09 02:37:10 -04:00
parent da5d310673
commit b18f7ca609
8 changed files with 1260 additions and 2 deletions
+66
View File
@@ -73,6 +73,10 @@ targets:
- package: swift-markdown
product: Markdown
- package: IndieAbout
# The Quick Look preview extension, embedded into `Contents/PlugIns/` — an appex is only ever
# found through the app that carries it, so this line is what makes pressing Space on a
# `.kanban` package in Finder reach `BoardPreviewProvider` at all.
- target: KanbanQuickLook
postBuildScripts:
- script: '"${SRCROOT}/../indie-skills/skills/app-versioning/scripts/update_build_info.sh"'
name: Update Build Info
@@ -106,6 +110,68 @@ targets:
SWIFT_STRICT_CONCURRENCY: complete
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
# MARK: - Quick Look preview extension
#
# **Space on a `.kanban` package in Finder** shows the board rather than a folder icon: its name,
# its tint, its lanes in order with each one's card count and first few card titles. A classic
# NSExtension appex (`com.apple.quicklook.preview`), embedded into the app above — the same
# arrangement Xcode's own "Quick Look Preview Extension" template produces, since Quick Look's
# preview point has no ExtensionKit spelling.
#
# **It compiles the shared storage engine verbatim, exactly as `KanbanMobile` does below** — the
# same source paths, not a copy and not a package — and for the same reason: a preview that read
# the format through its own parser would be a second reading of the storage contract, free to
# drift. `Kanban/Storage` is one compilation unit in practice (`FrontmatterDocument` reaches
# `IntegrityRules`, which reaches `BoardLoader` and `BoardWriter`), so it is listed whole; the
# paired `LiveStore/EchoLedger.swift` is `BoardWriter`'s one upward dependency, riding along here
# for the phone target's own stated reason. The write half is dead weight in a preview and stays
# dead: the entitlements are read-only, so nothing in here *could* write even if something called
# it.
#
# `STORAGE_ONLY` is what stops that one file dragging the whole live store in behind it. Its
# consumer-side sections speak `BoardDiff`/`ItemPath`/`CommentPath`, which reach `BoardStore` and
# everything under it; the phone excludes them with `#if os(macOS)` and cannot help here, because
# this target *is* macOS. So EchoLedger.swift carries the same gate on a second axis — platform,
# and layer — and this is the one target that sets it. See that file's "The reload's verdicts"
# comment for the full statement.
#
# `Kanban/UI/Palette.swift` is the one UI file it takes: the twelve-and-twelve name→hex table, so a
# board tinted `smokey-ocean` is the same colour in the preview as in the app. It is self-contained
# — AppKit, SwiftUI and `FieldValue`, nothing else in `Kanban/UI` — which is what makes a single
# file listable here at all. Nothing else from `Kanban/UI` compiles in: the preview is HTML, not a
# view hierarchy, and every other file up there reaches the store.
#
# The walk itself is `Kanban/Storage/BoardOutline.swift` — capped at every level, read-only, and
# unit-tested in `KanbanTests` where it can be, which is the whole reason it lives in the shared
# directory rather than in `KanbanQuickLook/`.
KanbanQuickLook:
type: app-extension
platform: macOS
sources:
- KanbanQuickLook
- path: Kanban/Storage
- path: Kanban/LiveStore/EchoLedger.swift
- path: Kanban/UI/Palette.swift
dependencies:
- package: Yams
- package: swift-markdown
product: Markdown
settings:
base:
# Prefixed by the host app's identifier, as the sandbox requires of an embedded extension.
PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.Kanban.QuickLook
MARKETING_VERSION: "2.0"
INFOPLIST_FILE: KanbanQuickLook/Info.plist
CODE_SIGN_ENTITLEMENTS: KanbanQuickLook/KanbanQuickLook.entitlements
GENERATE_INFOPLIST_FILE: false
SWIFT_STRICT_CONCURRENCY: complete
# The Info.plist's `NSExtensionPrincipalClass` is `$(PRODUCT_MODULE_NAME).BoardPreviewProvider`
# — stated here so the two can never drift apart silently.
PRODUCT_MODULE_NAME: KanbanQuickLook
# `$(inherited)` keeps the configuration's own `DEBUG` — this only *adds* the layer gate.
SWIFT_ACTIVE_COMPILATION_CONDITIONS: $(inherited) STORAGE_ONLY
# MARK: - Lanework for iPhone
#
# **The mobile app is a second product, not a second edition** (distinct bundle id, ruled