Files
projax/docs/standards/per.md
mAi 5dcacff520 feat(phase 4b): dark/light theme toggle + file-upload permanently out-of-scope
## Slice A — explicit dark/light toggle

projax now ships with two palettes and a 1y cookie to remember the choice.
Dark is the new default; ☀ button in the header nav flips to light and
writes projax_theme=light. Server reads the cookie via themeFromRequest(r)
and injects Theme + ThemeColor into every template via the centralised
render(w, r, …) path, so first paint never flashes the wrong theme. Inline
JS in layout.tmpl handles the toggle without a server roundtrip.

Every panel colour now lives in a CSS variable under
:root[data-theme=dark|light]; the only hardcoded hex values left are
inside those two :root blocks. A future palette tweak is one edit, not
30 selectors. Graph node colours, kind-badges, highlights and warn/ok/bad
all have parallel dark/light values picked for contrast.

Standalone SVG download bakes the light palette inline because the
downloaded asset has no parent :root providing vars — m's existing
snapshots stay print-friendly regardless of his current cookie.

Login page keeps its embedded dark CSS — it's the gateway, intentionally
always dark.

Tests: TestThemeDefaultIsDark, TestThemeCookieRoundTrips,
TestThemeCookieUnknownFallsBackToDark, TestThemeTogglePagesShareSameTheme,
TestThemeToggleScriptPresent, TestThemeColorMetaHelper. Full suite green.

## Slice B — file-upload permanently out of scope (m, 2026-05-17)

docs/design.md moves "File uploads / in-projax storage" from the §3c
parked list to a permanent "Out of scope (decided 2026-05-17)" clause
with the rationale: PER is the cross-reference index, not the file
system. docs/standards/per.md gains the same explicit clause so future
shifts working from the PER standard see the constraint where they
look. Memory note filed so future workers don't re-propose multipart
uploads, attachments tables, or documents buckets.

## docs/design.md §13 Theming

Documents the toggle approach, cookie semantics, palette table, the
standalone-SVG carve-out, the login-page exception, and the 4b
out-of-scope (prefers-color-scheme detection, per-page overrides,
transitions on swap).
2026-05-17 18:14:08 +02:00

8.3 KiB
Raw Blame History

projax External Reference (PER) Standard

Version: v0.1 Status: locked Authors: m + head Date: 2026-05-15

A projax External Reference (PER) is a short dotted string that m drops into letters, emails, invoices, bank transfers, PDF filenames, physical filing labels — anywhere a project (or a project-event) needs an unambiguous citation. The PER doubles as m's personal Aktenzeichen.

Format

<area>[.<project>[.<sub-project>...]][.<YYMMDD>][.<collision-tag>]
  • Lowercase internally (slug, used in DB, URLs, search index).
  • Display in m's preferred camelCase (mFin.house1.260515).
  • Lookup is case-insensitive — typing mfin.house1 resolves the same row as mFin.house1.
  • Slugs are vowel-elided where natural (prjx, mFin, mBrn).
  • Humanly readable UID is the explicit design goal: each PER is unique within projax (after collision-tag, if needed) AND legible at a glance to a human reading a letter.

Components

Segment Required Example Notes
Area yes dev, mFin, home Root-level area slug.
Project + sub-projects optional, dotted house1, springClean.bathroom Nests to any depth.
Date stamp optional 260515 YYMMDD. Anchors the PER to an event/document date.
Collision tag only when needed a, b, … Alpha sequence appended when the same <path>[.<date>] would otherwise repeat. See below.

Collision handling

When more than one artifact would otherwise share the same PER (same project path on the same date — or same project path with no date), append a single-letter tag in registration order: .a, .b, .c, … .z, then .aa, .ab if anyone ever needs it.

  • The first artifact gets the bare PER (no tag).
  • The second collision triggers .a for the new one. The first one retroactively gets .a only if there's a real risk of ambiguity in already-written documents (otherwise leave the first bare and start the second at .a — the registry resolves both).
  • Alpha (not numeric) is chosen so the tag never reads as an ordinal (.1 / .2 could be misread as "first / second meeting on that day" — alpha avoids that connotation).

Examples:

PER Meaning
mFin.house1.260515 the (only) thing about house1 on that day
mFin.house1.260515.a one of two+ things; this is the first letter / the buyer's notary draft
mFin.house1.260515.b the second one; the seller's response on the same day
Work.upc.deadlines.a one of two undated reference notes on this sub-project

Examples

PER Meaning
mFin the finances area at large
mFin.house1 the project
mFin.house1.260515 the project on 2026-05-15 (a letter, an invoice, a meeting note from that day)
Work.upc.deadlines.260520 a deadline reference on 2026-05-20
Home.springClean.bathroom a sub-project — no date
dev.prjx.260515 this very document, cited

Properties

  • Rename stability — when a slug is renamed, the previous slug lands in projax.items.aliases[]. Letters citing mFinanzen.house1.260515 continue to resolve after the rename to mFin.
  • Reverse search — drop a PER into projax search and it resolves the project (and any dated artifacts linked to that project on that date).
  • No primary-key role — PERs are NOT stored as primary keys. They're projections of (items.path, optional item_links.event_date). Canonical identity remains the UUID.
  • Stable over time — UUIDs are immutable. Slugs change; the PER stays valid via aliases[] resolution.

Where to use

  • Letter Aktenzeichen
  • Invoice "Reference" field
  • Bank transfer Verwendungszweck
  • Email subject prefix — [mFin.house1.260515] Kaufpreiszahlung
  • Physical document filing labels
  • PDF filenames — mfin.house1.260515.pdf
  • Calendar event titles where the project is the topic

Where NOT to use

  • Sensitive / external-facing contexts where revealing the project tree structure is undesirable (PERs leak m's life areas).
  • Form fields with strict character limits below ~2530 chars.
  • Anywhere ambiguity is a feature (intentionally vague references).

Schema implications

  • projax.item_links carries an optional event_date date column (migration 0011_item_links_event_date.sql, shipped 2026-05-15). Dated artifacts linked to an item — CalDAV todos, Gitea issues, document references, PER-cited letters — sit here with a date.
  • Day granularity is intentional. Time-of-day is not part of the PER standard.
  • Existing aliases text[] on projax.items is the rename-stability backbone. Don't drop on archive.
  • PER resolution = parse the string → match (area-walk-path, optional date) → return matching items_unified row + linked item_links rows with event_date = parsed-date.

Implementation (v0.1, shipped Phase 3c)

  • Backing columns: (projax.items.paths[], projax.item_links.event_date). The path is the canonical lookup key; the date narrows to a specific dated artifact.
  • Ref-type convention for the artifacts surfaced under a project's Documents section:
    • document — generic pointer (URL, file path, Drive link, …); ref_id is the pointer
    • note — short text snippet; ref_id is the body or a hash, full body in note column or metadata.body
    • url — bookmarked link; the UI renders ref_id as an <a> opening in a new tab
    • Existing typed refs (caldav-list, gitea-repo, gitea-issue, mai-project, …) keep their meaning and can also carry an event_date.
  • Collision tags are render-time only. When two links share (item_id, event_date), the UI appends .a/.b/… in created_at order. The first one stays bare. We never store the tag — re-rendering after a delete naturally rebalances the assignments.
  • URL routing: /i/<path>.<YYMMDD> first tries the literal path; if 404 and the trailing segment is a valid YYMMDD, retries against the stripped path and surfaces the date as a render hint so the Documents row gets .highlight. Invalid dates (Feb 30, etc.) hit the original 404 path.
  • MCP: mcp__projax__add_link accepts an optional event_date: "YYYY-MM-DD". linkView.event_date surfaces the stored value on responses.
  • Conflict policy: on (item_id, ref_type, ref_id, rel) duplicates the upsert uses COALESCE(new, old) for note and event_date, so a callable that re-adds a link without a date doesn't clobber a pre-set date.
  • Cross-references: see docs/design.md §"Documents / dated artifacts (Phase 3c)" for the schema delta and UI integration.

Display & UI

  • The backend stores lowercase. The frontend renders PERs in m's preferred camelCase by reading items.title (or a derived display_slug field if titles drift far from slugs).
  • Search input is normalized to lowercase before matching, so users can type either casing.

Out of scope (v0.1)

  • Document-type suffixes (.kauf, .notar, .mtg, .rcpt, etc.). Considered and dropped: the <area>.<project>.<YYMMDD>[.<collision>] form is enough granularity for m's use today, and adding a taxonomy of doc-types adds memorization overhead for marginal value. If multiple artifacts share <path>.<date>, disambiguation is handled by the collision tag (.a, .b, …), not by encoding a type into the PER string. Reserved for a future v0.2 if real usage surfaces the need.
  • Time-of-day suffixes — date granularity is enough. If m needs to distinguish "morning meeting" vs "evening call" on the same project on the same day, that's an internal metadata concern, not a PER concern.
  • Cross-PER references / linking syntax — Phase 2+.

Out of scope (permanent — decided 2026-05-17)

  • In-projax file storage / uploads. A PER names a document; it does not contain one. ref_id is a pointer (URL, file path, Drive link, mFin location, …) to where the file lives elsewhere. projax never grows a multipart-upload endpoint, an attachments table, or a documents bucket. m killed that direction explicitly on 2026-05-17; do not re-propose it. The source of truth for the bits stays wherever m already keeps it — projax is the cross-reference index, not the file system.

Versioning

PER is a forward-compatible additive format. A v0.2 may add suffixes or qualifiers; v0.1 PERs MUST continue to resolve under any future spec. No backwards-incompatible changes without bumping the major version.