toga-ai 1.0.510 → 1.0.511

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
- | [Tools (1.0 Internal-Tools App) Architecture](architecture.md) | **Tools** is a standalone 1.0 (`App_`) application that houses many small internal tools behind simple interfaces, gated by Client_True staff persona. | tools/index.php, tools/_/app/framework.php, tools/_/app/frameworkindex.php, tools/assets/img/favicon/favicon.ico, tools/assets/img/favicon/favicon-32x32.png, tools/assets/img/favicon/favicon-16x16.png, tools/assets/img/favicon/apple-touch-icon.png, tools/_/app/auth.php, tools/_/app/nav.php, tools/common/header.php, tools/common/footer.php, tools/mvc/get.php, tools/mvc/_TEMPLATE/get.php, tools/docs/ADDING_A_TOOL.md |
5
+ | [Tools (1.0 Internal-Tools App) Architecture](architecture.md) | **Tools** is a standalone 1.0 (`App_`) application that houses many small internal tools behind simple interfaces, gated by Client_True staff persona. | tools/index.php, tools/_/app/framework.php, tools/_/app/frameworkindex.php, tools/assets/img/favicon/favicon.ico, tools/assets/img/favicon/favicon-32x32.png, tools/assets/img/favicon/favicon-16x16.png, tools/assets/img/favicon/apple-touch-icon.png, tools/_/app/auth.php, tools/_/app/nav.php, tools/common/header.php, tools/common/footer.php, tools/mvc/get.php, tools/mvc/_TEMPLATE/get.php, tools/docs/ADDING_A_TOOL.md, tools/assets/css/theme.css, tools/assets/js/theme.js |
6
6
  | [ClickUp Sprint Dashboard (tools /clickup/react)](features/clickup-sprint-dashboard.md) | A native **ClickUp sprint dashboard** in the 1.0 `tools` app at route `/clickup/react`, built for a wall-mounted **TV display** ("TOGa IQ" internal sprint metri | tools/_/app/clickup/sprint.php, tools/v2/sprints/tile/index.php, tools/v2/sprints/current/index.php, tools/v2/sprints/status-breakdown/index.php, tools/v2/sprints/worktype-breakdown/index.php, tools/v2/sprints/points-by-dev/index.php, tools/v2/sprints/burndown/index.php, tools/assets/clickup/sprint-dashboard.html, tools/mvc/clickup/react/get.php, tools/mvc/login/get.php, tools/_/app/nav.php |
7
7
  | [CloudFront Client Setup](features/cloudfront-client-setup.md) | An SSO-gated admin tool at **`/devops/cloudfront-clients`** in the Tools 1.0 app that onboards a client onto **CloudFront + Route 53 across multiple AWS account | tools/_/app/devops/cloudfront.php, tools/mvc/devops/cloudfront-clients/get.php, tools/mvc/devops/cloudfront-clients/post.php, tools/assets/js/cloudfront-clients.js, tools/assets/css/cloudfront-clients.css, tools/_/app/nav.php, tools/_/app/frameworkindex.php, tools/config.production.ini |
8
8
  | [Design Demo Admin](features/design-demo-admin.md) | A self-serve admin UI at **`/design`** in the SSO-protected **Tools** app that lets the design team publish self-contained "Claude Design" HTML exports as **ver | tools/_/app/design/github.php, tools/mvc/design/get.php, tools/mvc/design/post.php, tools/assets/css/design.css, tools/assets/js/design.js, tools/_/app/frameworkindex.php, tools/_/app/nav.php, tools/composer.json |
@@ -14,4 +14,5 @@
14
14
  | [Tools SAML SSO Consumer & Persona-Gated Auth (App_Auth)](features/saml-sso-auth.md) | `App_Auth` is the Tools app's authentication layer: it consumes the SAML gateway `?saml=` handoff (see the 2.0 SAML downstream integration contract), establishe | tools/_/app/auth.php, tools/mvc/sso/initiate/get.php, tools/mvc/sso/get.php, tools/mvc/login/get.php, tools/mvc/login/post.php, tools/mvc/logout/get.php, tools/mvc/get.php, tools/config.production.ini, tools/config.local.ini |
15
15
  | [Talos Knowledge Base Admin UI (KB Documents + Vocabulary)](features/talos-kb-documents-admin.md) | > **PER-AI-MODEL, DATA-DRIVEN SCOPING (2026-07-29).** The KB-documents and Vocabulary admin > UIs were refactored from a single hard-coded **"development-team"* | tools/mvc/talos/kb-documents/get.php, tools/mvc/talos/kb-documents/post.php, tools/mvc/talos/knowledge-bases/get.php, tools/mvc/talos/knowledge-bases/post.php, tools/mvc/talos/vocabulary/get.php, tools/mvc/talos/vocabulary/post.php, tools/_/app/talos/s3.php, tools/_/app/talos/bedrock.php, tools/_/app/pg.php, tools/_/app/model/true/aimodels.php, tools/_/app/model/true/vectorindexes.php, tools/_/app/model/true/aimodels_vectorindexes.php, tools/_/app/worker.php, tools/_/app/nav.php, tools/config.production.ini, tools/config.alpha.ini, tools/.platform/httpd/conf.d/timeouts.conf, tools/.platform/hooks/prebuild/01-install-php-pgsql.sh, tools/.platform/hooks/postdeploy/01-restart-php.sh |
16
16
  | [Talos Pricing UI (Contracts, Pricing Dashboard, Usage, Settings + Estimator)](features/talos-pricing-ui.md) | The 1.0 (tools app) face of the **Talos Pricing Platform** — a "Talos Pricing" nav folder with four pages plus the estimate engine. | tools/_/app/nav.php, tools/_/app/talos/estimator.php, tools/_/app/talos/usage.php, tools/mvc/talos/contracts/get.php, tools/mvc/talos/contracts/post.php, tools/mvc/talos/pricing/get.php, tools/mvc/talos/usage/get.php, tools/mvc/talos/settings/get.php, tools/mvc/talos/settings/post.php, tools/assets/css/style.css |
17
+ | [App-Wide Colour Theme (light / dark / auto)](features/theme-light-dark.md) | A **light / dark / auto** colour theme for the *entire* Tools app, built as a single semantic-token layer (`assets/css/theme.css`) rather than per-page edits. | tools/assets/css/theme.css, tools/assets/js/theme.js, tools/_/app/frameworkindex.php, tools/_/app/nav.php, tools/common/header.php, tools/mvc/login/get.php, tools/assets/css/style.css, tools/assets/css/design.css, tools/assets/css/login.css, tools/assets/css/cloudfront-clients.css, tools/assets/css/gchart.css, tools/mvc/talos/usage/get.php, tools/mvc/talos/pricing/get.php, tools/mvc/talos/contracts/get.php, tools/mvc/clickup/dashboard/get.php, tools/mvc/email-migration/notify/post.php |
17
18
  | [Deploying Tools to Elastic Beanstalk (PHP 8.5 / Amazon Linux 2023)](workflows/deploy-to-elastic-beanstalk-al2023.md) | How the **Tools** 1.0 app boots on Elastic Beanstalk running `PHP 8.5 on 64bit Amazon Linux 2023/4.13.1 (aarch64)`. | tools/.ebextensions/004_http_to_https.config, tools/.ebextensions/006_mount-s3fs.config, tools/.ebextensions/007_setup_export_cache_folders.config, tools/.ebextensions/008_setup_ldap.config, tools/.ebextensions/009_setup_phpini.config, tools/.ebextensions/020_setup_git_libraries.config, tools/.ebextensions/050_register_instance_to_shared_application_load_balancer.config, tools/ebs/git.json, tools/ebs/setup_export_cache_folders.php, tools/_/app/frameworkindex.php |
@@ -6,7 +6,7 @@ project: Tools
6
6
  client: shared
7
7
  type: architecture
8
8
  status: active
9
- updated: 2026-07-24
9
+ updated: 2026-08-04
10
10
  owners: [jcardinal]
11
11
  files:
12
12
  - tools/index.php
@@ -23,7 +23,10 @@ files:
23
23
  - tools/mvc/get.php
24
24
  - tools/mvc/_TEMPLATE/get.php
25
25
  - tools/docs/ADDING_A_TOOL.md
26
+ - tools/assets/css/theme.css
27
+ - tools/assets/js/theme.js
26
28
  related:
29
+ - ./features/theme-light-dark.md
27
30
  - ./features/saml-sso-auth.md
28
31
  - ./features/persona-gated-navigation.md
29
32
  - ./features/developer-tools.md
@@ -47,7 +50,13 @@ double-gated (`[internal] dev_mode` AND `App_Registry::inDevMode()`). `config.*.
47
50
  (incl. `config.prod.ini`) is committed with plaintext production secrets that were **publicly
48
51
  web-readable** until the front-controller deny blocks added 2026-07-24 (see Known issues);
49
52
  rotation of every exposed secret is still owed. Never add more secrets, and keep config files
50
- denied at the front controller / out of the web root.
53
+ denied at the front controller / out of the web root. New CSS must use `--c-*` tokens from
54
+ `assets/css/theme.css`, never raw colour literals. **`assets/css/style.scss` is DEAD — edit
55
+ `style.css` directly** (the `.css` has drifted ~450 lines ahead; there is no sass binary or
56
+ `package.json`, so recompiling the SCSS would silently revert shipped work).
57
+ `App_FrameworkIndex::setHeaderCode()` is the **only** hook for markup in `<head>`, and it
58
+ **SETS rather than appends** — the theme bootstrap is its sole caller, so a second call
59
+ silently clobbers it.
51
60
 
52
61
  ## Boot & structure
53
62
 
@@ -72,6 +81,14 @@ sibling apps. A fixed left two-column shell: dark left sidebar (`.app-nav`) with
72
81
  persona-filtered nav in the middle, pinned footer with the user's name + a ghost "Log out"
73
82
  button; content area on the right. **No top header bar** (see Critical rules).
74
83
 
84
+ **All colour comes from a semantic token layer, `assets/css/theme.css`**, registered **first**
85
+ in `App_FrameworkIndex_Tools::__construct()` so every other sheet resolves through it. The app
86
+ supports light / dark / auto; the resolved theme is stamped on `<html>` as `data-theme` by an
87
+ inline pre-paint script and the user's choice lives in `localStorage` (`toga-tools-theme`) —
88
+ **not** the DB. The left sidebar and public header stay dark in **both** themes, which is why
89
+ `toga-brandmark-white.png` still works everywhere. Login shows `toga-horizontal-colored.png` in
90
+ light and `toga-horizontal-white.png` in dark. See `features/theme-light-dark.md`.
91
+
75
92
  ## Home route
76
93
 
77
94
  `/` (`mvc/get.php`) renders a persona-filtered dashboard of available tools (tiles grouped by
@@ -106,6 +123,11 @@ Create `mvc/<folder>/<tool>/get.php` (copy `mvc/_TEMPLATE/get.php`), add one ent
106
123
  composer package must `require_once` the autoloader itself.
107
124
 
108
125
  ## Change history
126
+ - 2026-08-04 — Added the app-wide light/dark/auto theme: `assets/css/theme.css` token layer
127
+ registered first in `App_FrameworkIndex_Tools`, pre-paint `data-theme` stamping via the
128
+ one-and-only `setHeaderCode()` hook, `localStorage` preference (no DB column), nav/public
129
+ header dark in both themes. Recorded that `style.scss` is dead and `setHeaderCode()`
130
+ sets-not-appends. Not committed to `tools` yet (jcardinal)
109
131
  - 2026-07-24 — Fixed the config-file web-root exposure: `config.*.ini` was publicly readable via
110
132
  the `!-f`-only front controller (`GET /config.production.ini` served it verbatim); added
111
133
  `<FilesMatch>` deny blocks referencing the new 1.0 back-end security standard. Softened the
@@ -6,8 +6,8 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-29
10
- owners: [kyalamarthi]
9
+ updated: 2026-08-04
10
+ owners: [kyalamarthi, jcardinal]
11
11
  files:
12
12
  - tools/_/app/clickup/sprint.php
13
13
  - tools/v2/sprints/tile/index.php
@@ -24,6 +24,7 @@ related:
24
24
  - ./persona-gated-navigation.md
25
25
  - ./saml-sso-auth.md
26
26
  - ./mvc-data-access-patterns.md
27
+ - ./theme-light-dark.md
27
28
  - ../architecture.md
28
29
  - ../../../2.0/apps/worker2/features/team-sprint-management.md
29
30
  ---
@@ -138,5 +139,15 @@ the React dashboard supersedes it.
138
139
  passed by reference", which the E_ALL error handler promotes to a fatal 500. Assign first:
139
140
  `$res = App_Database::query(...); buildArrayOfRows($res, false);`.
140
141
 
142
+ - **The old Power BI embed page (`mvc/clickup/dashboard/get.php`) keeps ~6 fixed colour
143
+ literals on purpose — do NOT theme them.** Its card/frame colour `#0b1020` is matched to the
144
+ embedded Power BI report canvas so nothing flashes white while the iframe loads. Themed
145
+ surfaces there would reintroduce the flash in one theme or the other. Everything else on the
146
+ page was tokenised for the app-wide theme — see
147
+ [App-Wide Colour Theme](./theme-light-dark.md).
148
+
141
149
  ## Change history
150
+ - 2026-08-04 — Tokenised the `/clickup/dashboard` page-local styles for the app-wide light/dark
151
+ theme, keeping ~6 deliberate fixed literals (incl. the `#0b1020` frame that matches the
152
+ embedded Power BI canvas so the iframe load does not flash white). Not committed. (jcardinal)
142
153
  - 2026-07-29 — Built `/clickup/react` as a native tools feature (data engine, `/v2/sprints/*` routes, CDN-React TV UI, iframe embed, "Dashboard/Clickup" nav); removed the old `/clickup/dashboard` Power BI nav entry; documented two reusable 1.0 gotchas (queryLogging preflight on Defaults-less DBs, buildArrayOfRows by-ref). (kyalamarthi)
@@ -208,5 +208,4 @@ None — internal/shared design-team tool.
208
208
  - [Tools Architecture](../architecture.md)
209
209
  - [Static Demo Hosting](../../../standalone/apps/forward/features/static-demo-hosting.md)
210
210
  - [Deploying Tools to Elastic Beanstalk (AL2023)](../workflows/deploy-to-elastic-beanstalk-al2023.md)
211
- </content>
212
- </invoke>
211
+
@@ -6,14 +6,15 @@ project: Tools
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-03
10
- owners: [bala]
9
+ updated: 2026-08-04
10
+ owners: [bala, jcardinal]
11
11
  files:
12
12
  - tools/mvc/email-migration/notify/get.php
13
13
  - tools/mvc/email-migration/notify/post.php
14
14
  - tools/_/app/nav.php
15
15
  related:
16
16
  - ./mvc-data-access-patterns.md
17
+ - ./theme-light-dark.md
17
18
  - ../workflows/deploy-to-elastic-beanstalk-al2023.md
18
19
  - ../architecture.md
19
20
  ---
@@ -104,7 +105,19 @@ endpoint that swallows non-fatal diagnostics (returns `true`) for the duration o
104
105
  fatals and thrown exceptions still reach the `catch`. The framework mechanism is documented in
105
106
  [mvc-data-access-patterns](./mvc-data-access-patterns.md).
106
107
 
108
+ ## Gotcha — the colour literals in `post.php` are EMAIL HTML; never tokenise them
109
+
110
+ `post.php` builds the digest **email** body. Email clients cannot resolve CSS custom
111
+ properties, so its colour literals must stay hard-coded — a `var(--c-…)` there renders as no
112
+ colour at all. This is easy to get wrong because the on-screen `get.php` in the same folder
113
+ *was* tokenised for the app-wide theme (see
114
+ [App-Wide Colour Theme](./theme-light-dark.md)). Rule: styles that leave the browser are not
115
+ themed.
116
+
107
117
  ## Change history
118
+ - 2026-08-04 — Tokenised the on-screen `get.php` page styles for the app-wide light/dark theme
119
+ and recorded that `post.php`'s email-HTML colour literals must stay hard-coded (email clients
120
+ cannot resolve CSS custom properties). Not committed. (jcardinal)
108
121
  - 2026-08-03 — Built the Legacy Email Notifier: multi-file xlsx merge via vendored PhpSpreadsheet,
109
122
  legacy-domain filter, `Client_True.Users` local-part resolution with always-domain-swap
110
123
  (never-skip), one digest per person per legacy domain, and chunked (25/batch) browser-driven
@@ -20,6 +20,7 @@ files:
20
20
  - tools/mvc/talos/settings/post.php
21
21
  - tools/assets/css/style.css
22
22
  related:
23
+ - ./theme-light-dark.md
23
24
  - ./persona-gated-navigation.md
24
25
  - ./mvc-data-access-patterns.md
25
26
  - ../architecture.md
@@ -97,6 +98,20 @@ about cost; cost is driven by tool-call **intensity** via the profile
97
98
  - **Voice multipliers (0.25 / 0.50) are UNVALIDATED** — supplied second-hand, never measured,
98
99
  and possibly inverted. Flagged in the UI. **Do not quote voice from them.**
99
100
  - **`db_team`** is read/write on Contracts and Settings, read-only on the dashboards.
101
+ - **Chart.js colours must come from theme tokens, and a live theme switch needs
102
+ `chart.update()`.** These pages now read series/axis/tick/gridline colours via
103
+ `TogaTheme.color('--c-…')` inside a `paint()` registered with `TogaTheme.onChange(paint)`,
104
+ then call `chart.update()` — `update()` clears Chart.js's resolver cache, so without it a
105
+ `Chart.defaults` change does not reach charts that already exist. Chart.js otherwise defaults
106
+ axis/tick text to a dark colour that is unreadable in dark mode. See
107
+ [App-Wide Colour Theme](./theme-light-dark.md).
108
+ - **These pages keep an explicit `--c-bs-*` token group** because they were authored against
109
+ Bootstrap's palette (`#0d6efd`, `#6c757d`, `#495057`, `#dc3545`, `#198754`…), which is close
110
+ to but not the TOGA brand palette — folding them into `--c-primary` visibly restyles them.
111
+ - **Open (dark mode):** the error toast measures ~2.4:1 (white on `--c-danger`) and likely
112
+ wants a fixed dark fill; the Pricing Dashboard target-band dataset never had a
113
+ `backgroundColor` (pre-existing authoring slip) so the band shades rather than tints; the
114
+ three chart pages cannot be statically proven light-identical and still need a browser pass.
100
115
 
101
116
  ## Security
102
117
  - `config.*.ini` carries committed plaintext secrets (team-accepted, see architecture Known
@@ -104,5 +119,10 @@ about cost; cost is driven by tool-call **intensity** via the profile
104
119
  (`[api] talos_backend_url` / `talos_backend_key`); no values recorded. Do not add secrets.
105
120
 
106
121
  ## Change history
122
+ - 2026-08-04 — Tokenised these pages for the new app-wide light/dark theme: page-local styles
123
+ now use `--c-*` tokens (Talos pages keep their own `--c-bs-*` Bootstrap ramp), and the three
124
+ Chart.js pages read colours via `TogaTheme.color()` + repaint on `TogaTheme.onChange()` with
125
+ `chart.update()`. Recorded the dark-mode open items (error-toast contrast, missing
126
+ target-band `backgroundColor`). Not committed. (jcardinal)
107
127
  - 2026-08-04 — Restructured to **Contracts / Pricing Dashboard / Usage / Pricing Settings**. New Contracts pages replace Onboarding with a real PROSPECT/ACTIVE/CHURNED lifecycle (signed contracts churn, never hard-delete — `TalosPricingBands` cascades; post-signature edits append to `TalosContractAmendments`), plus actual-vs-estimate variance, month history chart and one-click apply-recommended-org-fee. Settings replaces the Cost Factors editor with measured rows read-only (`UPDATE ... AND isDerived = 0`). Benchmarks page deleted. Pricing Dashboard rebuilt for sales leadership. Fixed `usage.php clientOptions()` reading an always-empty `TalosClients` (selector rendered empty) → `GET /api/usage/clients` with Team-DB fallback; added cost-intensity panel. Estimator moved to token unit price + workload profiles. Wrote the missing `.app-content:has(.tool--wide)` rule and consolidated Talos CSS into `style.css`. (jcardinal)
108
128
  - 2026-06-29 — Built the Talos Pricing UI: nav folder + 4 pages (Onboarding w/ live JS estimate and contract-signing that locks the band schedule into `TalosClients`+`TalosPricingBands` in one txn; read-only Pricing Dashboard + Usage Benchmarks; technical-only Cost Factors editor) and `App_Talos_Estimator` (reads `TalosCostFactors`, derives conversations/user so sales need not enter it). Reads Team DB via new `db_team` connection. Offerings CHAT/VOICE_TO_VOICE/NATURAL_VOICE = 1.0/0.25/0.50 (voice ambiguous, deferred). (jcardinal)
@@ -0,0 +1,209 @@
1
+ ---
2
+ title: App-Wide Colour Theme (light / dark / auto)
3
+ framework: "1.0"
4
+ repo: tools
5
+ project: Tools
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-08-04
10
+ owners: [jcardinal]
11
+ files:
12
+ - tools/assets/css/theme.css
13
+ - tools/assets/js/theme.js
14
+ - tools/_/app/frameworkindex.php
15
+ - tools/_/app/nav.php
16
+ - tools/common/header.php
17
+ - tools/mvc/login/get.php
18
+ - tools/assets/css/style.css
19
+ - tools/assets/css/design.css
20
+ - tools/assets/css/login.css
21
+ - tools/assets/css/cloudfront-clients.css
22
+ - tools/assets/css/gchart.css
23
+ - tools/mvc/talos/usage/get.php
24
+ - tools/mvc/talos/pricing/get.php
25
+ - tools/mvc/talos/contracts/get.php
26
+ - tools/mvc/clickup/dashboard/get.php
27
+ - tools/mvc/email-migration/notify/post.php
28
+ related:
29
+ - ../architecture.md
30
+ - ./persona-gated-navigation.md
31
+ - ./talos-pricing-ui.md
32
+ - ./clickup-sprint-dashboard.md
33
+ - ./legacy-email-notifier.md
34
+ - ./design-demo-admin.md
35
+ - ../../../standards/frontend.md
36
+ ---
37
+
38
+ ## Summary
39
+
40
+ A **light / dark / auto** colour theme for the *entire* Tools app, built as a single
41
+ semantic-token layer (`assets/css/theme.css`) rather than per-page edits. The user's choice
42
+ is a three-way switcher rendered next to their name in the left-nav footer, persisted in
43
+ **`localStorage` (`toga-tools-theme`) — deliberately not a DB column**, and applied
44
+ **before first paint** by an inline bootstrap script so there is no flash of the wrong theme.
45
+
46
+ **Light mode is a hard constraint: pixel-identical to before the change**, verified
47
+ mechanically (see [Verification](#verification-how-to-prove-light-mode-did-not-change)).
48
+ Any new page or stylesheet in this app should consume `--c-*` tokens and will then theme for
49
+ free.
50
+
51
+ > **Status (2026-08-04): NOT COMMITTED.** All of this lives in the working tree of the local
52
+ > `tools` checkout, which is on `_production` — it needs a feature branch before it lands.
53
+ > Verified **statically only**: resolver diffs, contrast maths, `php -l` on the 14 changed PHP
54
+ > files, `node --check` on `theme.js`. **No browser check and no specialist-reviewer pass has
55
+ > been run.** See [Open items](#open-items).
56
+
57
+ ## How it works
58
+
59
+ ### 1. The token layer — `assets/css/theme.css`
60
+
61
+ ~142 semantic CSS custom properties are declared on `:root` **with the original light
62
+ values**, and overridden in a `:root[data-theme="dark"]` block. It is registered **first** in
63
+ `App_FrameworkIndex_Tools::__construct()` so every other stylesheet resolves through it.
64
+
65
+ Token groups and their meaning:
66
+
67
+ | Group | Meaning |
68
+ | --- | --- |
69
+ | `--c-surface*` | fills |
70
+ | `--c-text*` | foregrounds |
71
+ | `--c-border*` | borders |
72
+ | `--c-primary*` | brand (`-tNN` = alpha tint) |
73
+ | `--c-nav-*` | left nav / public header — **dark in BOTH themes** |
74
+ | `--c-on-accent`, `--c-on-dark-tNN` | white in BOTH themes; content sitting on a permanently dark/coloured fill |
75
+ | `--c-slate-*`, `--c-indigo-*`, `--c-green-*`, `--c-red-*` | secondary ramp used by `design.css` + `cloudfront-clients.css` |
76
+ | `--c-bs-*` | legacy Bootstrap ramp, Talos pages only (see gotchas) |
77
+ | `--c-code-bg`, `--c-code-fg` | fixed dark code slab (not themed) |
78
+
79
+ `color-scheme` is itself tokenised as `--c-color-scheme` so native scrollbars and form
80
+ controls follow the theme. Note `style.css` previously hard-coded `color-scheme: light` on
81
+ `html, body`, which beat the `:root` token — that had to be removed.
82
+
83
+ ### 2. Pre-paint application (no flash)
84
+
85
+ `App_FrameworkIndex_Tools::themeBootstrapScript()` returns a tiny inline `<script>` injected
86
+ with `App_FrameworkIndex::setHeaderCode()`; `library` echoes it inside `<head>` **after** the
87
+ CSS/JS includes. It reads `localStorage` and stamps `data-theme` + `data-theme-pref` onto
88
+ `<html>` before the first paint. **An external JS file cannot do this** — it runs too late and
89
+ you get a flash of the wrong theme.
90
+
91
+ ### 3. Runtime API — `assets/js/theme.js` (`window.TogaTheme`)
92
+
93
+ - `get()` → `'light' | 'dark'` (resolved theme)
94
+ - `getPreference()` → `'light' | 'dark' | 'system'`
95
+ - `set(pref)` — persists + applies
96
+ - `onChange(fn)` — **fires immediately with the current theme**, then on every change, so a
97
+ caller has one code path for both "colour yourself now" and "recolour"
98
+ - `color('--token')` — read a themed colour out of CSS for JS-driven visuals
99
+
100
+ Persistence: `localStorage` key **`toga-tools-theme`**, values `light|dark|system`. Every
101
+ access is `try`/`catch`ed — Safari private mode *throws* rather than returning `null`.
102
+ `system` follows the OS live via a `matchMedia('(prefers-color-scheme: dark)')` change
103
+ listener (with the `addListener` fallback for Safari <14), and a `storage` event listener keeps
104
+ multiple open tabs in agreement.
105
+
106
+ ### 4. The switcher (server-rendered)
107
+
108
+ Rendered in `common/header.php` inside `.app-nav-footer` — next to the signed-in user's name,
109
+ above **Log out** — as a `role="radiogroup"` of three buttons carrying
110
+ `data-theme-choice="light|dark|system"` (labelled **Light / Dark / Auto**). `theme.js` manages
111
+ `aria-checked` + a roving `tabindex` and supports arrow keys. Icons come from a new
112
+ `App_Nav::themeIcon($theme)` that returns **inline SVG** from a fixed 3-key allowlist — inline
113
+ so the glyph inherits `currentColor`.
114
+
115
+ ### 5. Login-page wordmark
116
+
117
+ Two `<img>` wordmarks are in the markup and CSS shows one per theme
118
+ (`.login-logo--light` / `.login-logo--dark`, keyed off `:root[data-theme="dark"]`):
119
+ `toga-horizontal-colored.png` in light, `toga-horizontal-white.png` in dark.
120
+
121
+ Chosen over swapping `src` in JS or using `<picture>`: CSS honours an **explicit** Light/Dark
122
+ choice, whereas a `<picture>` media query only sees `prefers-color-scheme` and would ignore a
123
+ forced override — and CSS cannot flash the wrong logo. Both PNGs are 3610×700 so there is no
124
+ reflow. Tradeoff: the browser downloads both (~49 KB extra).
125
+
126
+ ### 6. Chart.js pages
127
+
128
+ `talos/usage`, `talos/pricing` and `talos/contracts` read series/axis/tick/gridline colours
129
+ from tokens via `TogaTheme.color()` inside a `paint()` function registered with
130
+ `TogaTheme.onChange(paint)`, followed by `chart.update()`. **`chart.update()` is required** —
131
+ it clears Chart.js's resolver cache so `Chart.defaults` changes take effect on charts that
132
+ already exist. Without this, Chart.js defaults axis/tick text to a dark colour that is
133
+ unreadable on a dark background.
134
+
135
+ ## Decisions
136
+
137
+ - **Left nav and public header stay dark in BOTH themes** (deepened for dark). Preserves the
138
+ app's identity, guarantees zero light-mode regression there, and keeps the existing white
139
+ brandmark PNG usable in both themes.
140
+ - **Preference is browser-local (`localStorage`), not a DB column** — explicit developer
141
+ requirement.
142
+ - **Light mode must be pixel-identical**, proven mechanically rather than by eye.
143
+ - **`assets/css/style.scss` left untouched** — neither revived nor deleted (out of scope; that
144
+ is already the status quo — see gotcha below).
145
+
146
+ ## Verification (how to prove light mode did not change)
147
+
148
+ 1. **Resolver diff.** Resolve every `var(--c-*)` back through the `:root` (light) block and
149
+ diff the result against the pre-change file. Identical output = light mode unchanged.
150
+ 2. **Dark-coverage assert.** Every `:root` token must have a `[data-theme="dark"]` override
151
+ unless it is intentionally fixed.
152
+ 3. **Per-file colour multiset vs. `git HEAD`.** Compare the *multiset* of resolved colour
153
+ values per file, not a line-by-line diff — an index/line-based diff is useless once lines
154
+ are inserted, whereas a multiset comparison is immune to line-number shifts.
155
+
156
+ ## Gotchas
157
+
158
+ - **`assets/css/style.scss` is DEAD in this repo — edit `style.css` directly.** Many recent
159
+ commits touch the `.css` while the `.scss` was last touched in an old ticket; the `.css` has
160
+ drifted ~450 lines ahead. There is no sass binary and no `package.json`. "Helpfully"
161
+ recompiling the SCSS would **silently revert a large amount of shipped work**.
162
+ - **`App_FrameworkIndex::setHeaderCode()` is the only hook for injecting markup into `<head>`
163
+ in 1.0** (library echoes `$this->headerCode` inside `<head>` after the CSS/JS includes). It
164
+ **SETS rather than appends**, and before this feature it was unused anywhere in tools — so a
165
+ **second caller silently clobbers the first**. If another feature needs head markup, refactor
166
+ to compose both strings; do not add a second `setHeaderCode()` call.
167
+ - **A colour literal used as both a foreground and a background must be split into two
168
+ tokens**, or dark mode inverts it the wrong way. Real cases in tools: `#10113A` (body text
169
+ *and* nav/public-header background), `#20242c` (text-strong *and* a dark chip fill), `#999`
170
+ (text grey *and* a badge fill), `#eef0f4` (border *and* badge fill), `#0f172a` (design.css
171
+ `--d-ink` text *and* the code-block background). Detect these **mechanically** — collect, per
172
+ candidate token, the set of CSS properties it appears with (background vs. colour/border).
173
+ Do not eyeball it.
174
+ - **A brand colour needs TWO tokens in a dark theme.** `--c-primary` (text/borders on a panel)
175
+ is **lightened** in dark for legibility; `--c-primary-fill` (a fill behind white text) is
176
+ kept **dark enough** in dark mode to preserve contrast. The roles pull in opposite
177
+ directions — one token makes white-on-brand buttons fail contrast.
178
+ - **The Talos admin pages keep their own `--c-bs-*` group.** They were authored against
179
+ Bootstrap's palette (`#0d6efd`, `#6c757d`, `#495057`, `#dc3545`, `#198754`…), which is close
180
+ to but **not** the TOGA brand palette; folding them into `--c-primary` visibly restyles those
181
+ pages. Only map a literal onto a token whose light value is **identical**.
182
+ - **`mvc/email-migration/notify/post.php` generates EMAIL HTML — its colour literals must stay
183
+ hard-coded.** Email clients cannot resolve CSS custom properties. It is the same file family
184
+ as the on-screen page, so it is easy to tokenise by mistake.
185
+ - **`mvc/clickup/dashboard/get.php` keeps ~6 fixed literals on purpose.** Its card/frame colour
186
+ `#0b1020` matches the embedded Power BI report canvas so nothing flashes white while the
187
+ iframe loads. Do not theme those.
188
+
189
+ ## Open items
190
+
191
+ - The three Chart.js pages **cannot** be statically proven light-identical — their colours are
192
+ now generated at runtime. Needs a browser pass.
193
+ - The Talos error toast measures ~2.4:1 in dark (white text on `--c-danger`) and probably wants
194
+ a fixed dark fill.
195
+ - `talos/pricing`'s target-band dataset never had a `backgroundColor` (a pre-existing authoring
196
+ slip), so its band renders as a faint darkening rather than a green tint.
197
+ - A few Talos overlay shadows shifted from black-based to slate-based (imperceptible but real),
198
+ and 7 imperceptible colour substitutions were accepted on Talos pages (largest:
199
+ `#f8f9fa` → `#f8f9fb`).
200
+
201
+ ## Change history
202
+ - 2026-08-04 — Built the app-wide light/dark/auto theme: `assets/css/theme.css` (~142 `--c-*`
203
+ tokens on `:root` + a `[data-theme="dark"]` override, registered first in
204
+ `App_FrameworkIndex_Tools`), `assets/js/theme.js` (`window.TogaTheme`), a pre-paint inline
205
+ bootstrap via `setHeaderCode()`, a Light/Dark/Auto radiogroup in the nav footer
206
+ (`App_Nav::themeIcon()`), per-theme login wordmarks, and token-driven Chart.js repaint on the
207
+ three Talos chart pages. Preference in `localStorage` (`toga-tools-theme`), no DB column; nav
208
+ + public header stay dark in both themes; light mode proven unchanged by resolver diff.
209
+ **Not committed — working tree on `_production`, no browser verification yet.** (jcardinal)
@@ -5,12 +5,13 @@ project: Library
5
5
  client: shared
6
6
  type: standard
7
7
  status: active
8
- updated: 2026-07-30
8
+ updated: 2026-08-04
9
9
  owners: [jcardinal]
10
10
  files: []
11
11
  related:
12
12
  - ./backend-php.md
13
13
  - ./framework-rules.md
14
+ - ../apps/tools/features/theme-light-dark.md
14
15
  - ../apps/tools/features/design-demo-admin.md
15
16
  - ../apps/tools/features/talos-kb-documents-admin.md
16
17
  ---
@@ -66,10 +67,48 @@ Each round trip adds a level. Equality comparisons then fail too, because a fres
66
67
  - `tools/_/app/design/github.php` — `App_Design_Github::decodeEntities()` applied to tab
67
68
  names and project titles on both the storage-read and request-read sides.
68
69
 
70
+ ## Tokenising an existing stylesheet for a second theme
71
+
72
+ When retrofitting light/dark onto a legacy sheet, four rules turn a guessing game into a
73
+ mechanical exercise.
74
+
75
+ **1. Split any literal used as both a foreground and a background into two tokens.** A single
76
+ token inverts the wrong way in the second theme. Detect these **mechanically** — collect, per
77
+ candidate token, the set of CSS properties it appears with (`background*` vs.
78
+ `color`/`border*`) — do not eyeball it. Real cases in `tools`: `#10113A` (body text *and* nav
79
+ background), `#20242c`, `#999`, `#eef0f4`, `#0f172a`.
80
+
81
+ **2. A brand colour needs TWO tokens.** `--c-primary` (text/borders on a panel) must be
82
+ **lightened** in dark for legibility; `--c-primary-fill` (a fill behind white text) must stay
83
+ **dark enough** in dark to preserve contrast. The roles pull in opposite directions; one token
84
+ makes white-on-brand buttons fail contrast.
85
+
86
+ **3. Only map a literal onto a token whose light value is IDENTICAL.** Measure perceptual
87
+ distance before accepting a "close enough" substitution — a near-match silently restyles the
88
+ page. Legacy pages authored against a *different* palette (the Talos admin pages use
89
+ Bootstrap's `#0d6efd`/`#6c757d`/`#dc3545`/`#198754`) keep their own token group with their
90
+ exact original values rather than being folded into the brand tokens.
91
+
92
+ **4. Prove the original theme is unchanged, mechanically.** (a) Resolve every `var(--…)` back
93
+ through the `:root` block and diff against the pre-change file; (b) assert every `:root` token
94
+ has a second-theme override unless intentionally fixed; (c) compare the per-file **multiset**
95
+ of resolved colour values against `git HEAD` — a line-index diff is useless once lines are
96
+ inserted, a multiset comparison is immune to line shifts.
97
+
98
+ Two things are **never** tokenised: HTML destined for an **email** (email clients cannot
99
+ resolve custom properties) and colours deliberately matched to third-party embedded content
100
+ (e.g. an iframe's canvas colour, to prevent a white flash on load).
101
+
102
+ Reference implementation:
103
+ [tools App-Wide Colour Theme](../apps/tools/features/theme-light-dark.md).
104
+
69
105
  ## Change history
70
106
 
107
+ - 2026-08-04 — Added "Tokenising an existing stylesheet for a second theme": split
108
+ dual-role colour literals, two tokens per brand colour, identical-value-only substitutions,
109
+ and the mechanical proof that the original theme is unchanged; plus the never-tokenise cases
110
+ (email HTML, colours matched to third-party embeds). Drawn from the `tools` light/dark
111
+ build. (jcardinal)
71
112
  - 2026-07-30 — Created. Establishes the escape-once/plain-text-storage invariant after a
72
113
  second independent occurrence in `tools` (Design Demo Admin, following the Talos
73
114
  vocabulary tool). (jcardinal)
74
- </content>
75
- </invoke>
@@ -14,7 +14,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
14
14
  - **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
15
15
  - **test** (Test) — 14 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
16
16
  - **toga** (TOGa) — 2 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
17
- - **tools** (Tools) — 13 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
17
+ - **tools** (Tools) — 14 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
18
18
 
19
19
  ## 2.0 framework
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.510",
3
+ "version": "1.0.511",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",