- brand.toml is the only place the product name lives; every crate and app
reads it through dac-brand (env vars with legacy fallback, settings folder
with copy-only migration, default library, preset extensions, i18n {app})
- cargo xtask brand check|test, run|install|package, docs, rename-crates,
deny and immich; packaging files are templates rendered from brand.toml
- icons regenerated from brand/icon.svg; upstream branding removed
- MIT licence (segment and fetch stay Apache-2.0); cargo deny licence policy
- clean-room rules in AGENTS.md/CLAUDE.md; docs/upstream-merge.md
- plan/ rebuilt and committed; parity tracker reopens Classic rows, adds
PLAN.md section 2 rows and the IMM Immich section; plan/immich.md and a
pinned Immich test server
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
14 KiB
Phase 0: Fork, brand configuration and foundations
Part of PLAN.md. Estimate: 12–22 agent-hours. Licence: MIT (decided 2026-10-10, PLAN.md §4). Unblocks: every later phase.
Goal: the LightCraft code base, running under a product name that lives in one config file and can be changed at any time. It also gets a parity tracker that covers Lightroom Classic and Immich, and an upstream-merge routine.
0.1 Fork
- Clone
../lightcraftinto this folder with full history:git clone ../lightcraft .- rename
origintoupstream - add the new origin.
- Tag the fork point
fork-basesogit log fork-base..upstream/mainalways shows what upstream did since. - Write
docs/upstream-merge.md. The routine:git fetch upstream- merge into a
merge/upstream-YYYYMMDDbranch - run
cargo xtask brand checkandcargo xtask ci - fix, commit, merge.
- Weekly at first.
- Upstream renames are resolved by the brand tooling in 0.2, never by hand-editing names.
Done when: cargo xtask ci is green on the untouched fork.
0.2 The name lives in one config file (brand.toml)
Requirement: the product name (and everything derived from it) is defined once in brand.toml at the
workspace root. Changing that file and rebuilding renames the product everywhere:
- window title, menus and About;
- CLI help;
- logs and settings folders;
- installers;
- MCP server name;
- docs that are generated.
No source file may contain the product name.
0.2.1 The config file
brand.toml (workspace root, the only place a name is written):
# Changing anything in [product] is safe at any time: rebuild and repackage.
[product]
display_name = "NoNameYet" # window title, menus, About, dialogs, docs
short_name = "NoNameYet" # macOS menu bar, taskbar, notifications
tagline = "Photo library and raw development"
vendor = "NoNameYet contributors"
homepage = "https://example.invalid"
repository = "https://example.invalid/repo"
binary = "nonameyet" # desktop executable name in packages
cli_binary = "nonameyet-cli"
env_prefix = "NONAMEYET" # NONAMEYET_LOG, NONAMEYET_GPU, …
mcp_server = "nonameyet" # MCP server id shown to agents
icon_svg = "brand/icon.svg" # source for every generated icon
logo_svg = "brand/logo.svg" # identity plate default, About box
# Identifiers stored in users' files and OS registrations. Changing these is allowed, but the old
# values MUST be appended to [legacy] in the same commit, so existing installs keep working.
[identity]
app_id = "org.example.NoNameYet" # reverse-DNS: bundle id, Flatpak id, MIME/desktop ids
settings_dir = "NoNameYet" # ~/.config/<x>, ~/Library/Application Support/<x>, %APPDATA%\<x>
library_default = "NoNameYet Library" # default catalog folder name under Pictures
catalog_ext = "nnycat"
preset_ext = "nnypreset"
url_scheme = "nonameyet" # nonameyet:// deep links, OAuth redirects
# Never derived from the product name: these stay valid across renames forever.
[stable]
xmp_namespace_uri = "https://ns.example.invalid/develop/1.0/"
xmp_namespace_prefix = "nnyd"
catalog_magic = "PHOTO-CATALOG" # file-format magic; neutral on purpose
# Previous identities, read on startup for migration (settings, catalogs, presets, env vars).
[legacy]
settings_dirs = ["LightCraft"]
env_prefixes = ["LIGHTCRAFT"]
preset_exts = ["lcpreset"]
catalog_exts = []
app_ids = []
Three groups, because names fall into three kinds:
- [product] is the user-visible brand, freely changeable.
- [identity] is OS and file identity. It is changeable, but it needs migration, which the app does automatically from [legacy].
- [stable] holds format identifiers that must never follow the name. A rename must never orphan users' XMP sidecars.
0.2.2 How code gets the name
- New crate
crates/brand(L0, no dependencies).- Its
build.rsreads../../brand.toml(or$BRAND_FILE, for test builds) and generatespub consts:DISPLAY_NAME,BINARY,APP_ID,ENV_PREFIXand the rest. - It also provides helpers:
brand::env("LOG")readsNONAMEYET_LOG, falling back to every legacy prefix;brand::settings_dir();brand::legacy_settings_dirs().
cargo:rerun-if-changed=brand.tomlmeans editing the file triggers a rebuild.
- Its
- Code changes:
- Replace every hard-coded name in
crates/andapps/(292 × "LightCraft" in 115 files, 25LIGHTCRAFT_*environment variables, preset and library names) withbrand::constants. - Replace user-visible format strings like
"{} Library"withbrand::LIBRARY_DEFAULT.
- Replace every hard-coded name in
- Localisation:
- Catalogs use a
{app}placeholder instead of the name; - the i18n loader substitutes
brand::DISPLAY_NAMEat load time. - Update every
docs/localization-*.mdtable.
- Catalogs use a
- Native macOS menu bar: take the application menu title from
brand::SHORT_NAME. - Packaging becomes templates rendered by
cargo xtask packagefrombrand.toml:packaging/linux/{app_id}.desktop.in,{app_id}.metainfo.xml.in,{app_id}.mime.xml.in;- Flatpak manifest,
nfpm.yaml.in; macos/Info.plist.in;windows/app.wxs.in.- The current files hard-code
ai.storyteller.lightcraft.*andlightcraft.wxs.
- Icons:
packaging/icons.shrenders every icon size frombrand.icon_svg. - Binaries:
- Cargo
[[bin]] names cannot read a config file, so the binaries keep neutral internal names (app,app-cli). cargo xtask run,cargo xtask packageandcargo xtask installcopy and rename them tobrand.binary/brand.cli_binary.- Shell completions and man pages are generated with the branded name.
- Cargo
- Crate names:
- Packages are named by function with a neutral internal prefix (
dac-raw,dac-catalog, …; the prefix is an internal codename never shown to users and can be changed withcargo xtask rename-crates <prefix>). - Renaming the crates is a separate one-time mechanical commit, done right after the fork, so upstream merges map
cleanly. See
docs/upstream-merge.md: the xtask also rewriteslightcraft_*/lightcraft-*paths in incoming upstream changes.
- Packages are named by function with a neutral internal prefix (
0.2.3 Migration on rename
On first start under a new [identity], the app checks every [legacy] entry in order:
- if a legacy settings folder exists and the new one does not, it copies the folder (never moves it) and writes a
migrated-frommarker; - it opens the legacy default library if no new one exists;
- it reads legacy preset extensions;
- it honours legacy env var prefixes, with a one-time deprecation log line.
LightCraft itself is listed in [legacy], so existing LightCraft users can switch with their settings and libraries.
0.2.4 Enforcement
cargo xtask brand check(part ofcargo xtask ci) fails if any of these appears outsidebrand.toml,[legacy]handling,ATTRIBUTION.md,docs/upstream-merge.mdorCHANGELOGhistory:- the current
display_name,binaryorenv_prefix; - any legacy name (
LightCraft,lightcraft,LIGHTCRAFT_,ArtCraft,storyteller).
- the current
- A CI job builds with a throw-away brand (
BRAND_FILE=xtask/test-brand.toml, display name "Zzyzx Test") and checks two things:- The headless UI snapshots (LightCraft already captures every widget through the control channel) contain "Zzyzx Test" and no other product name.
- Settings, logs and the default library go to Zzyzx paths.
- Docs: README and docs are written with the name in a
{{app}}template where it matters.cargo xtask docsrenders them; prose elsewhere says "the app".
Done when:
- Editing
display_nameinbrand.tomland runningcargo xtask runshows the new name in the title, menus, About, logs path, CLI--help, MCPserverInfoand installer metadata, with zero source edits. - The brand-check job is green.
0.3 Remove upstream branding
- Delete
docs/brand/(ArtCraft trademarks, not open licensed), the README hero and badges, Discord links and the "Crafting Apps" sections. - Remove the
contributors/andassets/ATTRIBUTION.mdentries that only concern ArtCraft branding. - Add our own placeholder
brand/icon.svgandbrand/logo.svg(original work) with attribution entries. - Keep the upstream copyright notices in
NOTICE/LICENSE-*; MIT and Apache require it.
0.4 Apply the licence: MIT (PLAN.md §4)
LICENSE:- MIT text, with the copyright lines "the LightCraft contributors" and "the PhotoCraft contributors" for inherited code, and "the project contributors" for new code;
- no product name.
- Remove
LICENSE-APACHEfrom the root and setlicense = "MIT"in[workspace.package]. - Apache-2.0-only crates keep their licence:
crates/segment(ported Hugging Face SAM 3 code) andcrates/fetchkeeplicense = "Apache-2.0", a local copy ofLICENSE-APACHEin each crate folder, and theirNOTICEentries;- Phase 1.7 writes the new
netcrate fresh under MIT instead of extendingfetch.
NOTICE: keep the upstream attributions, the Hugging Face notice and third-party data attributions.- Clean-room rules: keep LightCraft's
AGENTS.md/CLAUDE.mdnon-negotiables word for word and add:- darktable and Ansel may be used as running applications, manuals and prose design notes only;
- their source trees in
../darktableand../anselare off-limits to agents, as are GPL data tables (noiseprofiles.json,wb_presets.json,colormatrices.c, basecurves); - camera matrices are never harvested from Adobe-converted DNGs (check the
Softwaretag); camera-native DNGs are fine; - a "Sources" line in every PR that adds an algorithm (paper, prose spec, or "own design").
cargo denyin CI, so a GPL dependency can't arrive silently:- licences allowed: MIT, MIT-0, Apache-2.0 (incl. LLVM exception), BSD-2/3-Clause, ISC, Zlib, Unicode-3.0, CC0-1.0;
- everything else, including GPL, LGPL, AGPL and MPL, is denied.
assets/ATTRIBUTION.mdpolicy (unchanged): CC-BY and CC-BY-SA are allowed only as separate data or asset files, with attribution (for example the lensfun DB and GeoNames).
0.5 Rebuild plan/ and extend the tracker
LightCraft's tracker ids point to a local plan/ folder that wasn't published.
- Write
plan/from scratch, in our own words:lightroom-classic/(feature catalog, menus, Classic shortcuts, per-module behaviour notes);architecture.md(PLAN.md §5);STATUS.md;execution-plan.md(these phase files).
- Decide whether to keep
plan/gitignored. Recommendation: commit it. The ignore rule only existed to keep Adobe-observation notes local, so keep that one subfolder (plan/observations/) ignored. - Extend
docs/parity.md:- reopen the 🚫 Classic rows (KEYC-MODULES, the seven LRC-WEB rows, LRC-MAP-REVGEO);
- add rows for every PLAN.md §2 feature that has no row yet;
- fix the duplicated "Top gaps" list.
- Add a new tracker section "IMM. Immich integration" with the rows below. They are worked through in Phases 1,
3 and 4.
cargo xtask paritycounts them like any other row.
| Id | Feature | Tier | Phase |
|---|---|---|---|
| IMM-CONNECT | Connect to one or more Immich servers (URL + API key), version check, secure key storage | P1 | 1 |
| IMM-LINK | Link catalog photos to Immich assets by checksum; "in Immich" badge and filter | P1 | 1 |
| IMM-IMPORT | Immich as an import source (browse albums/timeline, download originals or add as linked) | P1 | 1 |
| IMM-EXTLIB | Shared-originals mode: Immich external library and catalog over the same folders, no duplicate uploads | P1 | 1 |
| IMM-SHARELINK | Create Immich shared links for published albums (from Web/Slideshow/Publish) | P2 | 3 |
| IMM-PUBLISH | Immich publish service: collections → albums, renders and/or originals, re-publish, stacks | P1 | 4 |
| IMM-SYNC | Two-way metadata sync (rating, favourite, title/description, tags ↔ keywords, albums ↔ collections, GPS, time, archive) | P1 | 4 |
| IMM-PEOPLE | Import Immich people and face regions into People; push names back | P2 | 4 |
| IMM-SEARCH | Immich smart (CLIP) and metadata search from the Library filter bar | P2 | 4 |
| IMM-MCP | Immich commands available via CLI, control channel and MCP | P2 | 4 |
- Write
plan/immich.md, the research note:- Immich concepts (assets, checksums, albums, tags, people, stacks, external libraries, shared links, API keys and permissions);
- the API endpoints we need, with the Immich release they were checked against;
- the minimum supported Immich version;
- how Immich's ratings and favourites behave.
- Licence note: the Immich server is AGPL-3.0. We only talk to it over HTTP and write our own client from its public API documentation. We don't vendor its code or its generated SDK.
- Add
xtask/immich/compose.yml, a pinned Immich release for local and CI integration tests, pluscargo xtask immich up|down|seed. The seed step loads a CC0 fixture set and creates an API key.
0.6 Shared infrastructure
craft-fontsas theCRAFT_FONTS_DIRbuild input (CJK glyphs), and the raw corpus viacargo xtask corpus.- Read and keep the never-crash standard (LightCraft's
AGENTS.mdalready includes its substance;craftrulesis not needed at build time).
Exit gate
cargo xtask cigreen under the configurable brand; brand-check job green with the throw-away brand.- Renaming the product = editing
brand.tomlonly (demonstrated in a PR that renames twice and back). - Legacy LightCraft settings and library are picked up on first start.
plan/exists;docs/parity.mdcontains Classic rows and IMM rows;cargo xtask parityprints the new totals.cargo xtask immich upstarts a test server; licence policy enforced bycargo deny.