1.1 Delete ColorScheme, the COLOR_SCHEME const, and the dead
window-linked bg vector-art block in src/app/schema/mod.rs; collapse
the if COLOR_SCHEME == ... background branch to the unconditional
video path. Verify make compile-dev in bin/app.
1.2 Collapse every match COLOR_SCHEME / if COLOR_SCHEME ==
two-arm branch in chat.rs, menu/{mod,channel,contact}.rs,
settings.rs, and wallet/*.rs to the DarkMode values inlined, and
drop the now-unused ColorScheme/COLOR_SCHEME imports (14 files
total). Verify make compile-dev and that the app renders as before.
2. Property default APIs and permissions
2.1 Add post-creation default installation on PropertyPtr in
src/prop/mod.rs (set_default_* for bool, u32, f32, str, enum, node
id, shape, null) mutating defaults[i] with no modify event, enforcing
the same length/type checks as the builder variants. Add unit tests:
install-on-live-node changes effective read; explicit value wins;
unset falls back to installed default.
2.2 Add expression defaults: builder set_defaults_expr plus
post-creation set_default_expr, and make is_expr/get_expr resolve
the effective expression source (vals expression, else defaults
expression). Update get_value so it never returns an unresolved
expression (resolution order per the prop-defaults spec; unevaluated
expression reads yield the type default). Unit tests: override+unset
returns to default expression; value-slot expression wins over default
expression; concrete-type read before first evaluation succeeds.
Verify cargo test prop:: in bin/app (crate darkfi-app) and
make compile-dev.
2.3 Make Role a hand-rolled u8 bitflag set (User, App, Internal,
Ignored, Theme — no new dependency) and add PropertyPermission
{read, write} taken by Property::new (transitional default = all
roles, so all current call sites keep behavior) plus can_read/
can_write and Error::PropertyPermissionDenied (design D13).
Enforce write masks centrally in every mutating API (role is already
a parameter) before any mutation or journaling; enforce read masks in
the wrap layer (wrap() validates upfront, get() checks), in
eval_f32_multi dependency reads, and at the net.rs RPC boundary;
exempt set_default_* and set_cache_*. Unit tests: denied write
leaves value unchanged; denied wrapped read errors; theme role denied
on a mask without Theme. Verify cargo test prop:: and make
compile-dev.
3. f32-array expression evaluation beyond rect
3.1 Generalize the PropertyRect::eval_with pattern in
src/prop/wrap.rs into a shared bounded f32-array evaluator and expose
PropertyColor::eval (globals-only, no extras) plus a single-f32
variant for font_size/spacing. Unit tests: per-index exprs recompute
from dependency globals; plain indices untouched; evaluated results
land in the cache and are returned by get_f32 (note: cache writes
bypass set_f32 range validation by design). Verify cargo test
prop::.
3.2 Add draw-path re-evaluation call sites in color/font-consuming
widgets (Text, Edit, ChatView, Menu, TextScramble): at the
top of draw(), alongside the existing rect evaluation, re-evaluate
expr-bound styled props (PropertyColor::eval, f32 eval) so every
pass computes from current dependency values; keep when_change
handlers invalidation-only (clear draw cache + trigger), with cache
writes as Role::Internal so when_change_external echo-skips them
(same pattern as rect evals today). Verify with a scratch wiring
(expr-bound text_color reacting to a test property) under make
compile-dev.
4. /theme token node and class-wiring helpers
4.1 Create the /theme node (linked at scene root beside
/setting) with an initial token vocabulary: text_color,
text_dim_color, accent_color, bg_color, edit.* colors,
menu.* colors, chatview.* colors, font/spacing f32s — defaults
form the neutral minimal palette; write mask = Theme only (D13).
Node is created in the app setup path before schema::make. Verify
node exists at /theme with defaults via a startup log or debug
lookup; make compile-dev.
4.2 Add wiring helpers (wire_color, wire_f32) that install
default-exprs + add_depend edges from a node property onto token
components in one call, replacing 4×set_f32 styling blocks. All
wiring happens at creation (design D5); theme-time rewiring goes
through ctx.depend_journaled. Verify a helper-wired text_color
tracks a token change at runtime under make compile-dev.
5. Minimal baseline: schema split and factory scrub
5.1 Scrub theme leaks in src/app/node.rs factories back to
neutral Tier-0 defaults: action_fg_color/action_bg_color,
url_copy_* colors, scramble_color, menu role1/role2 colors,
tokentable colors; enable allow_exprs() on every themeable prop
(colors, font_size, padding, lineheight, spacing) — it is
builder-only, so without this the wiring helpers' set_default_expr
calls fail with PropertySExprNotAllowed; and assign real
PropertyPermission masks per the D13 table (themeable style =
write App|Theme; widget-computed/runtime = write Internal without
Theme, enforcing D4; behavior toggles = write App; content/data =
write App) — replacing the transitional allow-all default. Verify
make compile-dev and note any visual deltas are restored later via
tokens/scifi.
5.2 Convert src/app/schema/mod.rs (root, netstatus icons and
overlay) to minimal defaults + token wiring: inline styling blocks
become set_default_* or helper wirings; geometry exprs move to
default-exprs; the first-run splash block is removed from the schema
(re-implemented by scifi in 7.1 per design D12). Verify both a
token-flip recolors the overlay and that with untouched tokens the UI
looks minimal-but-correct; make compile-dev.
5.3 Convert menu/ (mod, channel, contact, edit helpers) to
minimal defaults + tokens. Verify menu, channel/contact lists, and
edit mode render correctly under the baseline; make compile-dev.
5.4 Convert chat.rs per-channel screens (chatview styling, edit
composer, selection overlay) to minimal defaults + tokens, including
journaled handling for nick_colors (unbounded list, no exprs).
Verify a freshly joined channel's screen renders themed-if-tokens-set;
make compile-dev.
5.5 Convert wallet/ screens (main, send steps 1-4, receive,
tx_status, data, util helpers) and settings.rs to minimal defaults +
tokens. Verify full wallet flow walkthrough under baseline; make
compile-dev.
6. Theme engine
6.1 Create src/theme/ with the Theme trait (name(), async
apply(ctx)), compile-time registry (minimal, scifi), and
ThemeCtx using nodes-as-storage (design D8): tracked node roots
(incl. theme-defined token children under /theme/<name>, built
pre-Arc with builder add_property), theme tasks pushed onto
theme-owned nodes (push_task → cancelled by clear_tasks() on
unload), and a journal of Touched (prop, i — unset on unload),
List (unbounded-list priors, e.g. nick_colors — unbounded props
have no defaults tier), and Depend (theme-added edges — removed on
unload). All ctx setters stamp Role::Theme on the modify events
(attribution lives on the event stream since vals has none); verify
when_change_impl's equality filters pass Theme through like App
(only Internal/Ignored are skipped) and audit other Role consumers
(net.rs, setting.rs produce roles; any external Role mapping
needs a Theme arm); optional dev-mode leak audit warns on
Role::Theme events for (prop, i) pairs not in the touched-set. The
touched-set is required for correctness (vals has no authorship; a
deep-walk reset would destroy runtime state) — the ctx is the only
sanctioned path for themes to modify properties they don't own (and
D13 masks deny off-path writes outright). Unload order:
clear_tasks() on tracked nodes FIRST (cancel actors — an in-flight
watcher writing after its unset would leave stale vals) → unset
Touched → restore Lists → remove Depend edges → unset shared tokens →
unlink() per tracked node. Verify a synthetic theme that touches
one token, defines one private token wired to a widget, overrides a
bounded prop and an unbounded list, inserts one node, and pushes one
watcher task unloads cleanly (second apply leaves no residue, no
dangling edges, watcher cancelled) via a temporary schema-test hook
or unit test; make compile-dev.
6.2 Implement the atomic switch flow per design D9: single
redraw.make_guard batch (unload + apply), explicit trigger() after
the drop for structural-only changes (unlinks notify nothing); wire it
to the /setting/theme property — apply_startup() in App::setup
after schema::make, plus an engine-owned watcher task (not
theme-tracked) for live switches. Verify startup applies the persisted
theme before the first frame (pubsub queues buffer events until widget
tasks start; the first pass evaluates draw-side) and a live setting
change switches atomically; make compile-dev.
7. scifi theme
7.1 Extract the scifi look into src/theme/scifi.rs: cyan token
values, at least one theme-defined private token (proves the D6
private-token path, e.g. the netlogo accent), the king video
background as a tracked structural node (reserved low z band under
/window/content), the first-run scramble splash as a tracked node +
hide task gated on is_first_time (design D12), and the netstatus
overlay fade as a tracked watcher on is_visible writing alpha via
ctx.set_touched (remove the in-handler fade from the reconnect
click handler in src/app/schema/mod.rs). Verify: with theme=scifi
the app is pixel-comparable to today's look (video bg, splash on
first run, cyan accents, fade on overlay open); make compile-dev
and make compile-apk.
8. Theme setting and settings UI
8.1 Add the theme enum property to create_setting in
src/setting.rs (items minimal, scifi; default scifi); fix
Setting::new to skip-and-log persisted keys that no longer exist as
node properties instead of unwrapping; guard reads so an unknown
persisted theme falls back to the default. Verify persistence across
restart and the fallback path (hand-edit the persisted value);
make compile-dev.
8.2 Add enum rendering to the settings screen (settings.rs):
cycle-on-tap row for enum values (starts with theme; also fixes
net.transport showing "unknown"). Verify both enums display and
modify correctly, and switching theme there drives the live switch;
make compile-dev.
9. End-to-end verification
9.1 Full walkthrough under both themes (chat, menus, wallet send
flow, receive, settings, netstatus overlay, emoji picker): minimal is
complete and usable per the app-theme spec; scifi matches the
current shipped look; live switch mid-session leaves no mixed state,
no orphaned visuals, no running animations from the old theme; theme
writes outside permitted masks are denied (spot-check a widget-owned
prop via a debug theme write). Verify make compile-dev clean,
cargo test in bin/app green, and make compile-apk for Android.