Create the app icon and finalize versioning
App identity for both editions: the three-lane glyph
(rectangle.split.3x1.fill) over a gentle gradient - teal for base
Lanework, dark graphite for Lanework Pro - rendered as 1024 masters and
packaged down the full macOS ladder into each target's own iconset,
replacing the borrowed pathfinder icon.
The About window arrives via IndieAbout (0.2.x): icon, copyright,
version/build/date read from the Info.plist that update_build_info.sh
already stamps on every build (CFBundleVersion 160 = git commit count,
BuildDate, BuildHash - verified in the built product), the version line
opening the bundled CHANGELOG.md, and the new ISC LICENSE.md one
document link away. Both files are authored to the Apple
inline-Markdown subset (no # headings, single-line paragraphs) and
bundle into both editions.
CHANGELOG.md replaces its placeholder with the real 2.0 first-release
notes - one sentence per user-facing feature.
The About box is also an edition seam: base's EditionAbout carries the
one-line Pro signpost 12-editions.md allots ("Lanework Pro adds
git-backed board history and sync."), and the Pro target compiles its
own signpost-less twin instead of that file - the no-#if file-level
doctrine applied to a string. README gains the identity bullet.
1649 green on both schemes; verify-editions.sh 26/26.
Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
@@ -1,3 +1,29 @@
|
|||||||
**July 2026**
|
**July 2026**
|
||||||
|
|
||||||
Development begins — nothing user-facing yet.
|
Version 2.0: Lanework's first release — a kanban app whose boards are ordinary folders of Markdown files on your Mac.
|
||||||
|
|
||||||
|
Edit a board from any other app, script, or AI agent and the open window updates live; outside edits are first-class, never overwritten.
|
||||||
|
|
||||||
|
Cards open in their own window with a formatted preview, a Markdown editor with checkable task lists, and a raw-source view.
|
||||||
|
|
||||||
|
Attach files to a card by dropping them onto its face or into its card window.
|
||||||
|
|
||||||
|
Drag cards and lanes to rearrange them, move or copy them between boards, or drop files from Finder to create new cards.
|
||||||
|
|
||||||
|
Cut, copy, and paste cards and lanes — within a board, across boards, or as plain text into other apps.
|
||||||
|
|
||||||
|
Deleted cards land in a trash lane you can show beside your lanes; drag a card out (or cut and paste it) to restore it.
|
||||||
|
|
||||||
|
Undo and redo cover every board action, including bringing back a deleted lane with all its cards.
|
||||||
|
|
||||||
|
Start new boards from ten bundled templates, or save any board as a template of your own.
|
||||||
|
|
||||||
|
Search filters the board as you type, and new cards you create clear the filter so they never vanish under it.
|
||||||
|
|
||||||
|
Style cards and lanes from a twelve-color palette, tint lanes with a band, and give each board its own background.
|
||||||
|
|
||||||
|
The whole app works from the keyboard — arrow-key navigation, drag-free card moves, and Full Keyboard Access on every control.
|
||||||
|
|
||||||
|
VoiceOver reads boards as lanes of cards, announces outside changes in one polite digest, and names a focused card that was deleted externally.
|
||||||
|
|
||||||
|
Every board gets a guide file teaching AI agents the folder format, kept up to date automatically.
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import IndieAbout
|
||||||
|
|
||||||
|
/// **Base Lanework's About-box configuration — one of the two places base names Lanework Pro.**
|
||||||
|
///
|
||||||
|
/// 12-editions.md ▸ Quiet signposts: "Base presents as a complete app, not a demo: Pro is named
|
||||||
|
/// in exactly two places — one line in the About box, and the contextual popover line on `.git`
|
||||||
|
/// boards. Nothing on the welcome screen, nothing in banners." The popover line is
|
||||||
|
/// `BoardGitNote`'s; this is the About line, riding as the second line of the copyright text —
|
||||||
|
/// `IndieAbout` renders `copyrightText` as one multi-line `Text`, so the signpost is literally a
|
||||||
|
/// line in the box, no custom layout.
|
||||||
|
///
|
||||||
|
/// **The Pro target does not compile this file** (project.yml excludes it and compiles
|
||||||
|
/// `KanbanPro/Edition/EditionAbout.swift` instead): Pro's About has no signpost — the app it
|
||||||
|
/// would point to is the one showing the box — and falls back to the Info.plist copyright. The
|
||||||
|
/// difference is a file one target builds and the other does not, per the edition split's
|
||||||
|
/// no-`#if` doctrine (12-editions.md ▸ Targets).
|
||||||
|
enum EditionAbout {
|
||||||
|
|
||||||
|
/// The About window's content: version/build/date from the stamped Info.plist
|
||||||
|
/// (`update_build_info.sh` — CFBundleVersion, BuildDate, BuildHash), the version line
|
||||||
|
/// opening the bundled CHANGELOG.md, and the bundled ISC LICENSE.md as the one document
|
||||||
|
/// link. Both documents are authored to the Apple inline-Markdown subset — see the files'
|
||||||
|
/// own single-line paragraphs.
|
||||||
|
static var configuration: AppInfoConfiguration {
|
||||||
|
AppInfoConfiguration(
|
||||||
|
copyrightText: "© 2026 rzen\nLanework Pro adds git-backed board history and sync.",
|
||||||
|
documents: [.license(extension: "md")],
|
||||||
|
changelogDocument: .changelog()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 475 B |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 898 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 538 KiB After Width: | Height: | Size: 496 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
import IndieAbout
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// The scene graph (02-architecture.md § Windows, § Launch and window lifecycle).
|
/// The scene graph (02-architecture.md § Windows, § Launch and window lifecycle).
|
||||||
@@ -147,6 +148,13 @@ struct KanbanApp: App {
|
|||||||
/// whole of the contract, and the system supplies the rest.
|
/// whole of the contract, and the system supplies the rest.
|
||||||
@CommandsBuilder
|
@CommandsBuilder
|
||||||
private var menuCommands: some Commands {
|
private var menuCommands: some Commands {
|
||||||
|
// The About window (11-command-nexus.md files About under the app menu's standard
|
||||||
|
// furniture): icon, version/build/date from the Info.plist that `update_build_info.sh`
|
||||||
|
// stamped at build time — never a hardcoded string — with the version line opening the
|
||||||
|
// bundled changelog. `EditionAbout` is the edition seam: base's carries the one-line Pro
|
||||||
|
// signpost (12-editions.md ▸ Quiet signposts), Pro's twin does not.
|
||||||
|
IndieAboutCommand(configuration: EditionAbout.configuration)
|
||||||
|
|
||||||
// The File group, in 11-command-nexus.md's own row order: New Card, New Lane, New Board…,
|
// The File group, in 11-command-nexus.md's own row order: New Card, New Lane, New Board…,
|
||||||
// Open…, Open Recent ▸, Board Info, Duplicate, Save as Template, Reveal in Finder, Add
|
// Open…, Open Recent ▸, Board Info, Duplicate, Save as Template, Reveal in Finder, Add
|
||||||
// Attachment…, then the trash trio and Empty Trash…. The board-scoped ones validate against
|
// Attachment…, then the trash trio and Empty Trash…. The board-scoped ones validate against
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 512 B After Width: | Height: | Size: 500 B |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 911 B After Width: | Height: | Size: 866 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 538 KiB After Width: | Height: | Size: 531 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
import IndieAbout
|
||||||
|
|
||||||
|
/// **Pro's About-box configuration** — the twin of base's `Kanban/App/EditionAbout.swift`, which
|
||||||
|
/// the Pro target excludes (project.yml): same documents, no signpost line. Base's About carries
|
||||||
|
/// "Lanework Pro adds…" because base is where a user discovers Pro exists (12-editions.md ▸
|
||||||
|
/// Quiet signposts); Pro pointing at itself would be noise, so its copyright falls back to the
|
||||||
|
/// Info.plist string and the box is one line shorter.
|
||||||
|
enum EditionAbout {
|
||||||
|
|
||||||
|
/// See base's twin for the full story; the two must keep the same shape so `KanbanApp` can
|
||||||
|
/// read `EditionAbout.configuration` without knowing which edition it is in.
|
||||||
|
static var configuration: AppInfoConfiguration {
|
||||||
|
AppInfoConfiguration(
|
||||||
|
documents: [.license(extension: "md")],
|
||||||
|
changelogDocument: .changelog()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
**ISC License**
|
||||||
|
|
||||||
|
Copyright (c) 2026 Lanework Contributors
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
@@ -50,6 +50,8 @@ Lanework is in early development. This list tracks what has actually shipped and
|
|||||||
|
|
||||||
- **Accessibility** — the board is a real VoiceOver surface, not a grid of unlabelled rectangles: lanes are containers read as "⟨title⟩, lane, N cards" (the count is the filter's, like the visible badge), each card is one flattened element carrying its title, its attachment count and its cut-pending state, and traversal follows card `order` rather than masonry column position. VO-Space toggles selection through the same funnel a ⌘-click uses, context-menu rows double as custom actions, lane titles are headings for the rotor, and the trash column pins last. A **live board announces itself**: a foreign edit lands as one polite, non-interrupting digest per reload — "Board changed: 2 cards edited, 1 card added" — while the app's own writes stay silent. Which is which is decided **per file by the write-provenance ledger**, never by which kind of reload delivered it: a reconciling sweep on wake or reactivation announces whatever changed in the blind window (the app never vouches for changes it didn't witness), and a foreign edit that lands on a file the app had just written is still announced. A card that disappears under the cursor is named rather than merely lost ("Card 'Fix login' was deleted externally"), with focus recovering to its lane; when the lane went too, the announcement names the *lane* and its count and focus walks up then sideways to whatever now holds its position. Bracketed operations say one thing at completion and never their internal churn, and the banner strip is an announced element in its own right — the read-only lock and reload breakage speak when they appear and when they clear. **Every size in the app is relative**: there is not one hard-coded point size left — the card face, the lane header, the masonry, the style editor's wells and every window's floor derive from the system body font, so the whole board grows with the system text size while the no-horizontal-scroll rule holds (the lanes compress, the strip never scrolls) and titles keep truncating gracefully. The system's visual accommodations are wired throughout: **Increase Contrast** thickens every border and selection ring and gives card and lane plates an outline they don't otherwise have, **Reduce Transparency** turns the transient search bar's glass and the trash column's washes solid, and **Reduce Motion** has a variant for every animated surface in the app — movement goes instant, appear/disappear goes crossfade, uniformly, the live-reload seam included. **A coloured board computes its own text colour.** The board background is the one surface the app lets a colour sit behind text, so the ink is chosen rather than assumed: WCAG relative luminance against the ≥ 4.5:1 threshold, with an `#RRGGBBAA` value composited over the window background of the appearance you are actually in — so lane and trash headers take light or dark glyphs on their own and re-decide the moment you switch to Dark Mode. One path serves both halves of the styling vocabulary: the twelve palette wells are pinned by a test that checks the ink the app *picks* for each of them in both appearances (a dark palette board is now readable in Light Mode, which it was not), and a hand-written hex — which stays fully honoured from disk — gets the identical computation as it renders. Nothing is ever said by colour alone (selection is a ring plus a trait, a cut card is dimmed plus "cut, pending paste", the trash header is hatched plus labelled, a mixed batch reads "mixed"), and under **Full Keyboard Access** the board is a single visible tab stop with the arrow grammar inside it while every control around it — lane buttons, popovers, the style grids, welcome rows, template tiles — is Tab-reachable, arrow-navigable and labelled.
|
- **Accessibility** — the board is a real VoiceOver surface, not a grid of unlabelled rectangles: lanes are containers read as "⟨title⟩, lane, N cards" (the count is the filter's, like the visible badge), each card is one flattened element carrying its title, its attachment count and its cut-pending state, and traversal follows card `order` rather than masonry column position. VO-Space toggles selection through the same funnel a ⌘-click uses, context-menu rows double as custom actions, lane titles are headings for the rotor, and the trash column pins last. A **live board announces itself**: a foreign edit lands as one polite, non-interrupting digest per reload — "Board changed: 2 cards edited, 1 card added" — while the app's own writes stay silent. Which is which is decided **per file by the write-provenance ledger**, never by which kind of reload delivered it: a reconciling sweep on wake or reactivation announces whatever changed in the blind window (the app never vouches for changes it didn't witness), and a foreign edit that lands on a file the app had just written is still announced. A card that disappears under the cursor is named rather than merely lost ("Card 'Fix login' was deleted externally"), with focus recovering to its lane; when the lane went too, the announcement names the *lane* and its count and focus walks up then sideways to whatever now holds its position. Bracketed operations say one thing at completion and never their internal churn, and the banner strip is an announced element in its own right — the read-only lock and reload breakage speak when they appear and when they clear. **Every size in the app is relative**: there is not one hard-coded point size left — the card face, the lane header, the masonry, the style editor's wells and every window's floor derive from the system body font, so the whole board grows with the system text size while the no-horizontal-scroll rule holds (the lanes compress, the strip never scrolls) and titles keep truncating gracefully. The system's visual accommodations are wired throughout: **Increase Contrast** thickens every border and selection ring and gives card and lane plates an outline they don't otherwise have, **Reduce Transparency** turns the transient search bar's glass and the trash column's washes solid, and **Reduce Motion** has a variant for every animated surface in the app — movement goes instant, appear/disappear goes crossfade, uniformly, the live-reload seam included. **A coloured board computes its own text colour.** The board background is the one surface the app lets a colour sit behind text, so the ink is chosen rather than assumed: WCAG relative luminance against the ≥ 4.5:1 threshold, with an `#RRGGBBAA` value composited over the window background of the appearance you are actually in — so lane and trash headers take light or dark glyphs on their own and re-decide the moment you switch to Dark Mode. One path serves both halves of the styling vocabulary: the twelve palette wells are pinned by a test that checks the ink the app *picks* for each of them in both appearances (a dark palette board is now readable in Light Mode, which it was not), and a hand-written hex — which stays fully honoured from disk — gets the identical computation as it renders. Nothing is ever said by colour alone (selection is a ring plus a trait, a cut card is dimmed plus "cut, pending paste", the trash header is hatched plus labelled, a mixed batch reads "mixed"), and under **Full Keyboard Access** the board is a single visible tab stop with the arrow grammar inside it while every control around it — lane buttons, popovers, the style grids, welcome rows, template tiles — is Tab-reachable, arrow-navigable and labelled.
|
||||||
|
|
||||||
|
- **App identity — icon, versioning, About** — each edition carries its own icon (the three-lane glyph: teal for Lanework, graphite for Lanework Pro) and a real About window: icon, copyright, version and build stamped at build time from git (`CFBundleVersion` = commit count, plus `BuildDate` and `BuildHash` in the Info.plist — never a hardcoded string), the version line opening the bundled end-user changelog, and the ISC license one link away. Base's About carries the one quiet line naming Lanework Pro; Pro's carries none, per the quiet-signposts rule (DESIGN/12).
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
The Xcode project is generated — `project.yml` is the source of truth, not the `.xcodeproj`:
|
The Xcode project is generated — `project.yml` is the source of truth, not the `.xcodeproj`:
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ packages:
|
|||||||
swift-markdown:
|
swift-markdown:
|
||||||
url: https://github.com/apple/swift-markdown.git
|
url: https://github.com/apple/swift-markdown.git
|
||||||
minorVersion: 0.8.0
|
minorVersion: 0.8.0
|
||||||
|
# The About window (both editions): app icon, version/build from the stamped Info.plist, and
|
||||||
|
# the bundled LICENSE/CHANGELOG documents. Pre-1.0 like swift-markdown, same pinning logic.
|
||||||
|
IndieAbout:
|
||||||
|
url: https://git.rzen.dev/rzen/indie-about.git
|
||||||
|
minorVersion: 0.2.2
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
@@ -50,10 +55,20 @@ targets:
|
|||||||
- path: Templates
|
- path: Templates
|
||||||
type: folder
|
type: folder
|
||||||
buildPhase: resources
|
buildPhase: resources
|
||||||
|
# The About window's documents (LICENSE, CHANGELOG) — bundled so IndieAbout can render
|
||||||
|
# them in-app. Apple-inline-Markdown subset only: no `#` headings, no hard-wrapped
|
||||||
|
# paragraphs (every newline renders literally).
|
||||||
|
- path: LICENSE.md
|
||||||
|
type: file
|
||||||
|
buildPhase: resources
|
||||||
|
- path: CHANGELOG.md
|
||||||
|
type: file
|
||||||
|
buildPhase: resources
|
||||||
dependencies: &appDependencies
|
dependencies: &appDependencies
|
||||||
- package: Yams
|
- package: Yams
|
||||||
- package: swift-markdown
|
- package: swift-markdown
|
||||||
product: Markdown
|
product: Markdown
|
||||||
|
- package: IndieAbout
|
||||||
postBuildScripts: &updateBuildInfo
|
postBuildScripts: &updateBuildInfo
|
||||||
- script: '"${SRCROOT}/../indie-skills/skills/app-versioning/scripts/update_build_info.sh"'
|
- script: '"${SRCROOT}/../indie-skills/skills/app-versioning/scripts/update_build_info.sh"'
|
||||||
name: Update Build Info
|
name: Update Build Info
|
||||||
@@ -90,6 +105,10 @@ targets:
|
|||||||
- Kanban.entitlements
|
- Kanban.entitlements
|
||||||
- Assets.xcassets
|
- Assets.xcassets
|
||||||
- Assets.xcassets/**
|
- Assets.xcassets/**
|
||||||
|
# Base's About-box configuration carries the one-line Pro signpost (12-editions.md ▸
|
||||||
|
# Quiet signposts); Pro compiles its own `KanbanPro/Edition/EditionAbout.swift` twin
|
||||||
|
# without it — the edition split's file-level doctrine, applied to a string.
|
||||||
|
- App/EditionAbout.swift
|
||||||
# Pro's own source root — reserved now, filled by pro-m1. See KanbanPro/Edition/ProEdition.swift.
|
# Pro's own source root — reserved now, filled by pro-m1. See KanbanPro/Edition/ProEdition.swift.
|
||||||
- path: KanbanPro
|
- path: KanbanPro
|
||||||
excludes:
|
excludes:
|
||||||
@@ -99,6 +118,13 @@ targets:
|
|||||||
- path: Templates
|
- path: Templates
|
||||||
type: folder
|
type: folder
|
||||||
buildPhase: resources
|
buildPhase: resources
|
||||||
|
# The About documents, same files as base — one license, one changelog, two bundles.
|
||||||
|
- path: LICENSE.md
|
||||||
|
type: file
|
||||||
|
buildPhase: resources
|
||||||
|
- path: CHANGELOG.md
|
||||||
|
type: file
|
||||||
|
buildPhase: resources
|
||||||
dependencies: *appDependencies
|
dependencies: *appDependencies
|
||||||
postBuildScripts: *updateBuildInfo
|
postBuildScripts: *updateBuildInfo
|
||||||
settings:
|
settings:
|
||||||
|
|||||||