Build lane chrome — title bar, badge, inline rename
The lane title bar becomes real: leading SF Symbol (hand-written names render leniently, unknown ones fall back to the level default), title or secondary untitled placeholder, a quiet count badge that counts exactly the cards the body renders (so the m5 search filter is followed by construction), and a new-card button. The whole bar is the reorder drag surface — no grip — with click-vs-movement splitting select from drag; a pure proposal function maps the drag to an insertion index and release commits through the Writer's same-parent degenerate reorder, compacting and retrying when midpoint precision runs out. Clicking never edits: inline rename is Return on the sole selected card or Board > Rename for either kind, a third transient editor beside the placeholder that tracks its target by UUID, commits on focus loss, discards silently when the target vanishes, and removes the title key on an empty commit. The new-card placeholder renders at last — the settled Cmd-N target rule (pure, tested) files it after the anchor card, at a selected lane's bottom, or into the last-active lane; Return commits and re-selects the lane, Cmd-Return also opens the card window, and a failed create discards the overlay. New Card / New Lane / Rename land in the menus with focused-editor and read-only validation; rename gets its own WriteOperation case in the banner vocabulary. 59 new tests. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import AppKit
|
||||
|
||||
/// The `icon` field's rendering rule: **a name that resolves is drawn, anything else falls back to
|
||||
/// the level's default** (03-board-ui.md § Styling ▸ Capabilities, "`icon`: SF Symbol per item with
|
||||
/// per-level defaults").
|
||||
///
|
||||
/// ### Lenient, never an error
|
||||
///
|
||||
/// `icon` is a lenient field (01-storage-format.md § Frontmatter): a typo in a hand-written name is
|
||||
/// not a load failure, not a warning, and not an empty box — it renders as the default and the
|
||||
/// author's bytes are left exactly as written until they change them. That mirrors the read side's
|
||||
/// treatment of `width`, and it is what makes "any other SF Symbol name works written by hand"
|
||||
/// (03 § Controls) safe to promise: the app's curated grid is a convenience, the file is the escape
|
||||
/// hatch, and a name the running OS does not happen to have simply degrades.
|
||||
///
|
||||
/// `NSImage(systemSymbolName:)` is the only honest test — the symbol set is the *OS's*, and it grows
|
||||
/// between releases, so a hard-coded allow-list would be wrong the day after it was written.
|
||||
enum ItemSymbol {
|
||||
|
||||
/// The per-level defaults, verbatim from 03-board-ui.md § Styling ▸ Capabilities.
|
||||
static let board = "rectangle.split.3x1"
|
||||
static let lane = "square.stack"
|
||||
static let card = "doc.text"
|
||||
|
||||
/// `field`'s symbol name if it names a symbol this system can draw, `fallback` otherwise.
|
||||
///
|
||||
/// Handles all three `FieldValue` shapes the same way, which is the point: a missing key, a
|
||||
/// malformed one (a sequence where a scalar belongs), and a valid-but-unknown name are one
|
||||
/// case to the renderer — *there is no symbol to draw, so draw the default*.
|
||||
static func name(_ field: FieldValue<String>, fallback: String) -> String {
|
||||
guard let name = field.value, exists(name) else { return fallback }
|
||||
return name
|
||||
}
|
||||
|
||||
/// Whether the running system can draw `name` as an SF Symbol.
|
||||
///
|
||||
/// Uncached deliberately. The lookup is a bundle-backed symbol resolution that AppKit itself
|
||||
/// caches, it runs once per lane per render pass, and a cache here would be one more piece of
|
||||
/// main-actor state to keep honest across the OS's own symbol availability. If a profile ever
|
||||
/// says otherwise, this one function is where the memo goes.
|
||||
static func exists(_ name: String) -> Bool {
|
||||
!name.isEmpty && NSImage(systemSymbolName: name, accessibilityDescription: nil) != nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user