toga-ai 1.0.433 → 1.0.435

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.
@@ -3,6 +3,7 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
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 |
6
+ | [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 |
6
7
  | [Tools — Developers Folder (UUID & Password Generators)](features/developer-tools.md) | The first two tools shipped in the Tools app, both under the **Developers** folder and gated to personas **Development Team** / **TOGa Technology**. | tools/mvc/developers/uuid/get.php, tools/mvc/developers/password/get.php |
7
8
  | [Tools MVC — Routing, CSRF & App_Database Access Patterns](features/mvc-data-access-patterns.md) | The load-bearing 1.0 (`App_`) framework conventions a developer needs when adding a page to the Tools app — URL routing, CSRF, and DB access through `App_Databa | tools/_/app/nav.php, tools/mvc/get.php |
8
9
  | [Tools Persona-Gated Navigation (App_Nav)](features/persona-gated-navigation.md) | `App_Nav` is the Tools app's two-level, **persona-gated** navigation. | tools/_/app/nav.php, tools/mvc/get.php |
@@ -91,6 +91,16 @@ Create `mvc/<folder>/<tool>/get.php` (copy `mvc/_TEMPLATE/get.php`), add one ent
91
91
  **accepted this for now** (developer decision, 2026-06-25). Future remediation: git-ignore the
92
92
  config files and rotate all exposed credentials. (Location only — no values recorded here.)
93
93
  - **SSO initiation + replay defense are open items** — see `features/saml-sso-auth.md`.
94
+ - **Framework-level Sentry error reporting is an open gap.** Prod printed "Sentry is not
95
+ installed" because `sentry/sentry` was missing from `composer.json` (added `^4.10`; sibling
96
+ apps use `^4.10`/`^4.19`). But even with Sentry in `vendor/`, the 1.0 framework does **not**
97
+ auto-require `vendor/autoload.php` (the `App_Talos_S3` pattern `require_once`s it explicitly),
98
+ so framework-level Sentry reporting for Tools still does not load. Any code path that needs a
99
+ composer package must `require_once` the autoloader itself.
94
100
 
95
101
  ## Change history
102
+ - 2026-07-24 — Added the SSO-gated **Design Demo Admin** tool at `/design` (`App_Design_Github`
103
+ + `mvc/design/*` + namespaced assets + a `design` nav group), which publishes versioned demos
104
+ into the `forward` repo over the GitHub API (see `features/design-demo-admin.md`). Noted the
105
+ framework-level Sentry gap (composer autoloader not auto-required) in Known issues (jcardinal)
96
106
  - 2026-06-26 — Documented app-local favicons under `assets/img/favicon/` (registered in `_/app/frameworkindex.php`, not the shared `resources/` set) + the per-app-asset convention; refreshed the home-route description for one-step SSO sign-in and the persona-less empty-state (jcardinal)
@@ -0,0 +1,171 @@
1
+ ---
2
+ title: Design Demo Admin
3
+ framework: "1.0"
4
+ repo: tools
5
+ project: Tools
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-24
10
+ owners: [jcardinal]
11
+ files:
12
+ - tools/_/app/design/github.php
13
+ - tools/mvc/design/get.php
14
+ - tools/mvc/design/post.php
15
+ - tools/assets/css/design.css
16
+ - tools/assets/js/design.js
17
+ - tools/_/app/frameworkindex.php
18
+ - tools/_/app/nav.php
19
+ - tools/composer.json
20
+ related:
21
+ - ../architecture.md
22
+ - ../features/persona-gated-navigation.md
23
+ - ../features/saml-sso-auth.md
24
+ - ../workflows/deploy-to-elastic-beanstalk-al2023.md
25
+ - ../../../standalone/apps/forward/features/static-demo-hosting.md
26
+ ---
27
+
28
+ ## Summary
29
+
30
+ A self-serve admin UI at **`/design`** in the SSO-protected **Tools** app that lets the
31
+ design team publish self-contained "Claude Design" HTML exports as **versioned demos** — no
32
+ developer, no git, no local checkout. It writes into the **`agilantsolutions/forward`** repo
33
+ (branch `_main`) over the **GitHub REST API**, and the demos stay **publicly hosted by
34
+ `forward`** at `demo.togatech.com` (URLs unchanged). See
35
+ [Static Demo Hosting](../../../standalone/apps/forward/features/static-demo-hosting.md).
36
+
37
+ The tool **moved out of `forward` into `tools` this session.** Rationale: `forward` has no
38
+ auth (it is only a forwarder/hoster); `tools` is SSO-protected and is the home for internal
39
+ tools. The admin UI is now behind SSO, but the data (demos + manifests) still lives in and is
40
+ served from the `forward` repo.
41
+
42
+ ## Key files / entry points
43
+
44
+ - `_/app/design/github.php` — class **`App_Design_Github`**: all GitHub REST + domain logic
45
+ ported from the old single-file tool (`ghApi` / `ghListDir` / `ghReadFile` /
46
+ `ghCommitFiles` / `ghDeletePaths`, project/version/tab CRUD, upload validation, redirect-
47
+ stub builder).
48
+ - `mvc/design/get.php` — route `/design` (page shell). Self-guards with
49
+ `App_Auth::requireAuth([...])` + `App_Page::validateCrossSiteRequestForgery()`.
50
+ - `mvc/design/post.php` — AJAX action dispatcher.
51
+ - `assets/css/design.css`, `assets/js/design.js` — namespaced under `.design-tool`,
52
+ registered globally in `_/app/frameworkindex.php`.
53
+ - `_/app/nav.php` — a `design` group, action **`Design Demos`** → `/design`, personas
54
+ `['TOGa Technology','Development Team']`.
55
+
56
+ ## How it works
57
+
58
+ ### GitHub API access (no filesystem)
59
+
60
+ The tool runs on the SSO-protected Tools EB host, not a git checkout, so **all** repo access
61
+ is over the GitHub API:
62
+
63
+ - **Reads / listing:** GitHub **Contents API** (`ghReadFile` uses the raw media type
64
+ `Accept: application/vnd.github.raw` so multi-MB exports stream up to 100 MB — the base64
65
+ Contents response inlines only ≤1 MB and silently returns empty content above that).
66
+ - **Writes:** GitHub **Git Data API** — blob → tree → commit → update-ref — so a publish is
67
+ an **atomic multi-file commit** (version files + regenerated stub + `project.json` land in
68
+ one commit).
69
+ - Publishing pushes to `forward`'s `_main`, which triggers `forward`'s EB auto-deploy — demos
70
+ go live in a few minutes; there is no staging step.
71
+
72
+ ### Data model (lives in the `forward` repo)
73
+
74
+ - Per project: **`<project>/project.json`** — `{ title, latest, hidden, tabs[], versions[] }`.
75
+ Human detail (label/author/notes/date) lives in the manifest, not the URL. A directory is a
76
+ managed demo **iff it contains `project.json`**.
77
+ - Per version: **`<project>/vN/index.html`** — sequential `/v1`, `/v2`, `/v3` URL folders
78
+ (not semver, not date-based).
79
+ - Repo root **`tabs.json`** — `{ "tabs": [...] }`, the ordered tab list.
80
+ - The bare `/<project>` serves an auto-generated `index.html` **stub** that
81
+ meta-refresh/JS-redirects (relative `./vN/`) to the newest **visible** version; regenerated
82
+ on every publish/unlist.
83
+
84
+ ### Multi-tab membership
85
+
86
+ The project.json `tab` **scalar** became a **`tabs` array**. It is read **backward-
87
+ compatibly** (an old `tab` scalar is honored) and **lazily migrated** — the scalar is dropped
88
+ on the next write of that project.json. Tab management via a per-pill "⋯" menu:
89
+
90
+ - **Rename / delete** — rewrite `tabs.json` **and** every affected `project.json` in one
91
+ **atomic** commit.
92
+ - **Reorder** — a single-file `tabs.json` commit, validated as a **permutation** of the
93
+ existing list.
94
+
95
+ ### Actions & UX
96
+
97
+ - **Publish a version** — uploads a self-contained HTML export as the next `vN/index.html`,
98
+ appends a version entry, regenerates the stub, commits.
99
+ - **Remove version** — collapses the version out of the main list; unlisted versions sit
100
+ behind a per-project "Show N unlisted" collapsible.
101
+ - **Unlist a project ("Remove")** — reversible `hidden` flag; files are not deleted.
102
+ - **Hard-delete a whole project** — `ghDeletePaths()` reads the head tree recursively,
103
+ rebuilds a fresh tree **excluding the project folder** (exact folder-boundary prefix match),
104
+ commits, then PATCHes the ref. Offered alongside the reversible unlist.
105
+ - **Set latest / toggle version / edit version / toggle project / set project tabs** — commit
106
+ the relevant `project.json` (metadata-only edits do not rebuild the stub).
107
+ - **Copy URL** and **Copy Claude Design prompt** — clipboard helpers.
108
+ - **Single scroll region** — `#projects` is the only scroll area; header/tabbar are fixed, so
109
+ switching tabs never shifts layout.
110
+ - The Claude Design export **prompt modal** forces a dark `<pre>` background + light text with
111
+ `!important` (the global light `pre` theme otherwise wins).
112
+
113
+ ### Hardening
114
+
115
+ - `isValidProjectName()` guards on `setLatest` / `toggleVersion` / `editVersion` /
116
+ `toggleProject` / `setProjectTabs`.
117
+ - `upload()` **errors on corrupt project.json** instead of silently resetting history.
118
+ - Inline `window.DESIGN_CONFIG` uses `JSON_HEX_*` flags.
119
+ - Input length caps, version-id regex guard, HTML content sniff on upload; no client-facing
120
+ leakage of GitHub/curl error detail.
121
+
122
+ ## Access control
123
+
124
+ Behind Tools SSO. The page self-guards with `App_Auth::requireAuth([...])` and
125
+ `App_Page::validateCrossSiteRequestForgery()`; the nav action is gated to the
126
+ `TOGa Technology` and `Development Team` personas. This is the key improvement over the old
127
+ `forward` tool, which had **no** authentication.
128
+
129
+ ## GitHub token
130
+
131
+ `App_Design_Github::token()` reads the token from `getenv()` **AND** `$_SERVER` **AND**
132
+ `$_ENV`, then falls back to a `[design] github_token` config key for local dev. The EB
133
+ environment property is named **`GITHUB_TOKEN`** (value not recorded here). It is never
134
+ logged or echoed. Reading all three superglobals is required because php-fpm on AL2023
135
+ defaults to `clear_env=yes` — see the deploy workflow gotcha. Prefer a **fine-grained PAT**
136
+ scoped to the `forward` repo's Contents R/W.
137
+
138
+ ## Client variations
139
+
140
+ None — internal/shared design-team tool.
141
+
142
+ ## Gotchas / known issues
143
+
144
+ - **`curl_close()` is deprecated as of PHP 8.5** (and a no-op since 8.0). The 1.0 framework
145
+ on PHP 8.5 promotes deprecations to `ErrorException`, which aborts the request — every
146
+ GitHub call threw and the tool returned "Unexpected error" (0 projects). Fix: removed all
147
+ `curl_close()` calls. Audit ported code for other deprecated-in-8.x calls.
148
+ - **Contents API inlines only ≤1 MB** — read exports with the raw media type (see above).
149
+ - **Publishing pushes to `forward`'s `_main`** and triggers EB auto-deploy; no staging.
150
+ - Never hardcode the token in tracked source — a leaked `ghp_`/PAT must be rotated.
151
+
152
+ ## Change history
153
+
154
+ - 2026-07-24 — Moved the Design Demo Admin from `forward` into the SSO-protected `tools` app
155
+ (`App_Design_Github` + `/design` MVC route + namespaced assets + nav entry); still commits
156
+ to `agilantsolutions/forward` `_main` and demos stay hosted at `demo.togatech.com`. Added
157
+ multi-tab membership (`tabs` array, backward-compatible + lazy migration), tab
158
+ rename/delete/reorder via ⋯ menu (atomic multi-file commits; reorder validated as a
159
+ permutation), hard project delete (`ghDeletePaths` tree rebuild), unlisted-version collapse,
160
+ and a permanent single scroll region. Hardened project-name guards, corrupt-manifest
161
+ handling, and `JSON_HEX_*` inline config. Token now read from `getenv()`/`$_SERVER`/`$_ENV`
162
+ (`GITHUB_TOKEN` EB property) for the AL2023 php-fpm `clear_env` case; removed `curl_close()`
163
+ for PHP 8.5. (jcardinal)
164
+
165
+ ## Related docs
166
+
167
+ - [Tools Architecture](../architecture.md)
168
+ - [Static Demo Hosting](../../../standalone/apps/forward/features/static-demo-hosting.md)
169
+ - [Deploying Tools to Elastic Beanstalk (AL2023)](../workflows/deploy-to-elastic-beanstalk-al2023.md)
170
+ </content>
171
+ </invoke>
@@ -55,7 +55,9 @@ The old approach ran `ebs/setup_phpini.php` (now superseded) which called
55
55
  failed silently (it was wrapped in `ignoreErrors: true`), so `include_path` never picked up
56
56
  `/var/www/library`, producing fatal `require_once('_.php')` errors at runtime.
57
57
 
58
- The `application.ini` sets `include_path = ".:/var/www/library"`, upload/post limits (16M),
58
+ The `application.ini` sets `include_path = ".:/var/www/library"`, upload/post limits
59
+ (`upload_max_filesize=25M` / `post_max_size=30M`, raised from 16M so multi-MB design-demo HTML
60
+ exports up to ~25MB post through — see `features/design-demo-admin.md`),
59
61
  `memory_limit = 2G`, `display_errors`, the `redis.so` extension, and the rediscluster session
60
62
  handler (`session.save_path` points at the ElastiCache cluster cfg endpoint). Session GC
61
63
  maxlifetime is 4 days.
@@ -100,6 +102,12 @@ setup — the git clone (`020`) deliberately does **not** use it (see gotchas).
100
102
 
101
103
  ## Gotchas (durable AL2023 / EB rules)
102
104
 
105
+ - **php-fpm on AL2023 defaults to `clear_env=yes`, so `getenv()` cannot see EB environment
106
+ properties.** An EB environment property (e.g. `GITHUB_TOKEN`) that is set correctly still
107
+ returns `false` from `getenv()` under php-fpm on AL2023, because the FPM pool strips the
108
+ environment. Read such a value from **`getenv()` AND `$_SERVER` AND `$_ENV`** (the property
109
+ reaches PHP via the superglobals even when `getenv()` is blind), and fall back to a config
110
+ key for local dev. Never log/echo the value.
103
111
  - **Write new-deploy files to `/var/app/ondeck/<path>`, never `/var/www/html/<path>` during
104
112
  `container_commands`.** While container_commands run, `/var/www/html` still symlinks to the
105
113
  **old** app. After the deploy swap, `/var/app/ondeck` becomes `/var/app/current`
@@ -128,6 +136,9 @@ setup — the git clone (`020`) deliberately does **not** use it (see gotchas).
128
136
  returns `200` fast. Reusable for any TOGA app behind Elastic Beanstalk.
129
137
 
130
138
  ## Change history
139
+ - 2026-07-24 — Added the php-fpm `clear_env=yes` gotcha (read EB env properties from
140
+ `getenv()`/`$_SERVER`/`$_ENV`) and raised `application.ini` upload/post limits to 25M/30M for
141
+ multi-MB design-demo exports (jcardinal)
131
142
  - 2026-06-26 — CONFIRMED + RESOLVED the SAML SSO return-leg gotcha: the `tools.togatech.com` EB environment had no HTTPS (443) listener at all; adding the HTTPS:443 listener + ACM cert covering the exact return hostname fixed the SSO return leg with no code change (jcardinal)
132
143
  - 2026-06-26 — Added the SAML SSO return-leg gotcha: a "connection timeout" on `https://<host>/sso` was a missing EB/ALB HTTPS (443) listener for the new `togatech.com` hostname (not PHP/SAML); documented the curl `http=000` vs `127.0.0.1` 200 diagnosis (jcardinal)
133
144
  - 2026-06-26 — Documented the AL2 → AL2023 (PHP 8.5) EB migration for Tools: package renames
@@ -112,6 +112,20 @@ are adding a column that only `worker` reads, the `.sql` file goes in `dbchanges
112
112
  Never create a `.sql` file inside a 1.0 application repo. The SQL that belongs in those
113
113
  repos is only query strings embedded in PHP code — not standalone migration files.
114
114
 
115
+ ## PHP 8.x deprecations abort the request (audit before running on 8.5)
116
+
117
+ The 1.0 framework's error handler promotes PHP **deprecation notices to `ErrorException`**,
118
+ which aborts the request. On PHP 8.5, a single deprecated call in a live code path is therefore
119
+ a hard failure, not a silent notice — it surfaces to users as a generic error.
120
+
121
+ - When porting or running 1.0 code on PHP 8.x, **audit for deprecated-in-8.x calls** in every
122
+ reachable code path before deploy.
123
+ - Known offender: **`curl_close()`** — deprecated as of PHP 8.5 and a no-op since PHP 8.0.
124
+ Remove all `curl_close()` calls; the cURL handle is closed automatically. (This exact bug
125
+ made the Design Demo Admin tool throw on every GitHub call and render 0 projects.)
126
+ - Do not suppress the deprecation with `@` (see the coding-style rules) — remove/replace the
127
+ deprecated call instead.
128
+
115
129
  ## Error handling in controllers
116
130
 
117
131
  Controllers must not let exceptions bubble up to the framework unhandled. Catch specific exceptions, log them, and return an appropriate response.
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-07-23
9
+ updated: 2026-07-24
10
10
  owners: ["jcardinal", "kyalamarthi"]
11
11
  files:
12
12
  - worker2/Worker/Team/Sprint.php
@@ -178,7 +178,28 @@ All sprint state lives in the Team database (`_underscore::DB_TEAM`). Core table
178
178
  (`reworkEvents`, `hygieneMisses`, `unjustifiedStatusChanges`,
179
179
  `unjustifiedWorkEffortChanges`, `timeInProgressThisSprint`). Task statuses use the
180
180
  `_Model_Team_Task::STATUS_*` constants (e.g. `STATUS_IN__DONE`).
181
- - **`Tasks_Developers`** — task ↔ developer assignment join table.
181
+ - **`Tasks_Developers`** — task ↔ developer assignment join table. This is a **many-to-many**
182
+ relationship: a task can have several assigned developers. Any per-developer points rollup
183
+ (e.g. "points by dev") credits **each** assigned developer the task's *full* points — the
184
+ points are not split across assignees.
185
+
186
+ **Column vocabularies & datetime columns (dashboard-relevant).** For current-state reporting the
187
+ exact values matter:
188
+
189
+ - **`workTypeNow` / `workTypeAtLock`** — enum `COMMITTED / CONDITIONAL / STRETCH / UNPLANNED`.
190
+ MySQL string comparison is **case-insensitive** by default, so `'Committed'`/`'STRETCH'`/etc.
191
+ all match regardless of case.
192
+ - **`statusNow` / `statusAtLock` / `statusAtEnd`** — free-form varchar, values stored
193
+ **lower-case**: `complete`, `to do`, `in progress`, `stage review`, `hotfix review`,
194
+ `back-end review`, `ui review`, `on hold`, `roadblocked`, `awaiting client`, `rework`,
195
+ `pseudocode`, `ongoing`, `archived` (list is open-ended — treat unknown values as "review", see
196
+ the status rollup below).
197
+ - **`dtDone` vs `dtCompleted` are two different datetime columns** and are **not**
198
+ interchangeable. `dtDone IS NOT NULL` is the Power BI "done" test used by the KPI tiles and
199
+ burndown; `dtCompleted IS NOT NULL` is the "Complete" test used by the per-dev segment
200
+ breakdown. Do not substitute one for the other.
201
+ - **`sprintPointsNow` / `sprintPointsAtLock`** — the points measured by every dashboard metric
202
+ below use `sprintPointsNow` (current state), not the at-lock value.
182
203
 
183
204
  `CaptureSprintEnd()` and `CaptureSprintDaily()` are the private sync routines that walk the
184
205
  ClickUp sprint list (paginated, `include_timl=true`), upsert `Sprints`/`Developers`/`Tasks`/
@@ -249,8 +270,102 @@ Neither is "wrong" — they answer different questions (scored performance vs. l
249
270
  If a tile or report must **agree with TOGa IQ's canonical scoring**, reconcile it to definition
250
271
  (1); otherwise expect current-state tiles to diverge from sprint scores.
251
272
 
273
+ ## Power BI dashboard metric definitions (DAX → SQL)
274
+
275
+ These are the **current-state / Power BI-parity** definitions behind the TOGa IQ sprint
276
+ dashboard (the same set of tiles and charts the Power BI report shows). They translate the
277
+ Power BI DAX into SQL against the `Team` schema so any consumer — a worker2 report, an api2
278
+ Record Script tile, or a local prototype — produces numbers that match Power BI. All of them
279
+ use current-state columns (`workTypeNow`, `statusNow`, `sprintPointsNow`) except where noted,
280
+ and follow the **Power BI / current-state definition** above (`dtDone IS NOT NULL`), not the
281
+ canonical scoring definition.
282
+
283
+ ### KPI tiles — points by work type
284
+ Per tile: `points = SUM(sprintPointsNow)`, `done = dtDone IS NOT NULL`, filtered by a work-type
285
+ column + value. **Non-uniform on purpose — the column differs by tile:**
286
+
287
+ - **Committed / Stretch / Unplanned** filter on **`workTypeNow`** (`'Committed'` / `'STRETCH'` /
288
+ `'UNPLANNED'`).
289
+ - **Conditional** filters on **`workTypeAtLock` = `'Conditional'`** — a *different column*
290
+ (the at-lock baseline), not `workTypeNow`. Miss this and the Conditional tile is wrong.
291
+
292
+ MySQL string comparison is case-insensitive, so casing of the literal does not matter.
293
+
294
+ ### Status rollup (status pie) — "Status now Category Group" DAX SWITCH
295
+ A `SWITCH` over `statusNow` (case-insensitive) into five buckets:
296
+
297
+ | Bucket | `statusNow` values |
298
+ |--------|--------------------|
299
+ | **Complete** | `complete`, `archived` |
300
+ | **In Progress** | `in progress`, `rework` |
301
+ | **Stalled** | `on hold`, `roadblocked`, `awaiting client` |
302
+ | **To Do** | `to do` |
303
+ | **Review** | **everything else (SWITCH default)** — all the review-type statuses fall here |
304
+
305
+ The default → **Review** case is load-bearing: any new/unknown status is a Review, not an error.
306
+ Shown by **task count** (dashboard.html) or by **`SUM(sprintPointsNow)`** (dashboard1.html donut
307
+ variant) — same rollup, different measure.
308
+
309
+ ### Work Type pie
310
+ `SUM(sprintPointsNow) GROUP BY workTypeNow` — all four work-type values live in `workTypeNow` for
311
+ this chart (unlike the KPI tiles, Conditional is *not* read from `workTypeAtLock` here).
312
+
313
+ ### Sprint Points By Dev — "New Status" DAX SWITCH
314
+ Points = `sprintPointsNow`; each task is assigned **one** segment via a SWITCH evaluated in order:
315
+
316
+ 1. `dtCompleted IS NOT NULL` → **Complete** (note: `dtCompleted`, not `dtDone`).
317
+ 2. else `statusNow NOT IN ('to do','in progress','rework','on hold','roadblocked','awaiting client')`
318
+ → **In Review**.
319
+ 3. else → the task's **`workTypeNow`** value (Committed / Conditional / Unplanned).
320
+
321
+ Joined `Tasks → Tasks_Developers → Developers` and grouped by developer. Because the join is
322
+ many-to-many, each assigned developer is credited the task's **full** `sprintPointsNow`.
323
+
324
+ ### Sprint Burndown (over working days)
325
+ Plotted over **working days only** (Mon–Fri, ~10 per two-week sprint; call the count `N`,
326
+ `dayNumber` 1-based):
327
+
328
+ - **Target line:** `CCU_total × (1 − dayNumber / N)`, clamped to `≥ 0`.
329
+ - **Committed actual:** `CCU_total − (CCU points with dtDone on or before that day)`.
330
+ - **All actual:** `CCUS_total − (CCUS points done-to-date)`.
331
+ - Actual series **stop at today** (no future points).
332
+
333
+ Two work-type universes: **CCU** = Committed + Conditional + Unplanned; **CCUS** = CCU + Stretch.
334
+
335
+ ### Time Progression
336
+ Business-hours elapsed vs. total. Working days are Mon–Fri only, each **8h (09:00–17:00)**, so a
337
+ 10-working-day sprint = **80h**. `% = elapsed business hours ÷ 80`, using the local clock.
338
+
339
+ ### Current-sprint auto-resolution
340
+ Dashboards must **never hardcode a sprint number.** Resolve the current sprint as:
341
+
342
+ 1. the sprint whose date range contains today: `CURDATE() BETWEEN dateStart AND dateEnd`;
343
+ 2. else the most recently started: `ORDER BY (dateStart <= CURDATE()) DESC, dateStart DESC LIMIT 1`.
344
+
345
+ In the local prototype this is exposed as `GET /v2/sprints/current`, and a `/v2/sprints`
346
+ middleware defaults every tile/chart endpoint to it when no `?sprint=` is supplied.
347
+
348
+ > **Prototype provenance & credentials.** These definitions were reverse-engineered and verified
349
+ > against the live `Team` schema (which resolves to the **core cluster reader** — see
350
+ > [per-client database connections](../../_underscore/features/per-client-database-connections.md))
351
+ > by a local Node/React + Express (mysql2) prototype that stands in for the api2 `/v2` engine and
352
+ > returns the standard api2 envelope. The prototype's DB credentials live in an **uncommitted
353
+ > `.env`** (never in the repo or this doc). When productionized, the tiles become api2 Record
354
+ > Scripts registered via **dbchanges2** — see
355
+ > [Record Scripts](../../api2/features/record-scripts.md).
356
+
252
357
  ## Change history
253
358
 
359
+ - 2026-07-24 — Documented the full **Power BI dashboard metric definitions (DAX → SQL)** for the
360
+ TOGa IQ sprint dashboard: KPI tiles by work type (with the non-uniformity that **Conditional
361
+ filters on `workTypeAtLock`** while Committed/Stretch/Unplanned filter on `workTypeNow`), the
362
+ status-rollup SWITCH (default → Review), the work-type pie, the per-dev "New Status" segment
363
+ SWITCH (`dtCompleted`-based, many-to-many full-points-per-dev), the burndown target/actual over
364
+ working days (CCU vs CCUS), time-progression (Mon–Fri 8h days, 80h/sprint), and current-sprint
365
+ auto-resolution. Enriched the data model with column vocabularies (workType/status enums), the
366
+ **`dtDone` vs `dtCompleted`** distinction, and the Tasks↔Developers many-to-many points rule.
367
+ Reverse-engineered/verified via a local Node/React + Express prototype (creds in uncommitted
368
+ `.env`). (kyalamarthi)
254
369
  - 2026-07-23 — Recorded that two intentionally-different "committed/done" definitions coexist:
255
370
  the canonical sprint-scoring definition (`statusNow IN (STATUS_IN__DONE)` + `workTypeAtLock`,
256
371
  incl. the reusable `_pointsByWorkType`/`_tasksByWorkType` helpers on `_Model_Team_Sprint` in
@@ -13,7 +13,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
13
13
  - **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
14
14
  - **test** (Test) — 13 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
15
15
  - **toga** (TOGa) — 2 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
16
- - **tools** (Tools) — 8 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
16
+ - **tools** (Tools) — 9 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
17
17
 
18
18
  ## 2.0 framework
19
19
 
@@ -37,7 +37,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
37
37
 
38
38
  - **togatech** (TOGA Technology Website) — 4 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
39
39
  - **websocket** (WebSocket Server) — 2 doc(s) → [standalone/apps/websocket/INDEX.md](standalone/apps/websocket/INDEX.md)
40
- - **forward** (Forwarder) — 4 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
40
+ - **forward** (Forwarder) — 3 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
41
41
 
42
42
  ## Clients
43
43
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
- | [Forwarder Architecture](architecture.md) | Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded redirect domains. | forward/forward.ini, forward/index.php, forward/.htaccess, forward/.platform/httpd/conf.d/rewritemap.conf, forward/.ebextensions/rewritemap.config, forward/design/index.php, forward/composer.json |
6
- | [Design Demo Admin Tool](features/design-demo-admin.md) | A standalone, dependency-free PHP tool served at `https://demo.togatech.com/design` that lets the **design team** publish self-contained "Claude Design" HTML ex | forward/design/index.php, forward/tabs.json, forward/design/.config.example.php, forward/.gitignore, forward/.htaccess, forward/.ebextensions/php-uploads.config |
5
+ | [Forwarder Architecture](architecture.md) | Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded redirect domains. | forward/forward.ini, forward/index.php, forward/.htaccess, forward/.platform/httpd/conf.d/rewritemap.conf, forward/.ebextensions/rewritemap.config, forward/composer.json |
7
6
  | [Encrypted-Link Handler](features/encrypted-link-handler.md) | A `index.php` feature in [Forwarder](../architecture.md) for redirect domains whose URL path carries an **encrypted token** that must be decoded before redirect | forward/index.php |
8
7
  | [Static Demo Hosting](features/static-demo-hosting.md) | A lightweight way to host self-contained static HTML pages (demos, exported designs, download landing pages) on the Forwarder app under a clean URL — e.g. | forward/.htaccess, forward/togadesk/index.html |
@@ -14,7 +14,6 @@ files:
14
14
  - forward/.htaccess
15
15
  - forward/.platform/httpd/conf.d/rewritemap.conf
16
16
  - forward/.ebextensions/rewritemap.config
17
- - forward/design/index.php
18
17
  - forward/composer.json
19
18
  related: []
20
19
  ---
@@ -76,7 +75,13 @@ The `.htaccess` at the repo root drives a plain-text lookup table:
76
75
  (e.g. `sos.*` → `/sos/`), it redirects to `/<subdomain>/` and serves that dir's
77
76
  `index.html`. Used for the bundled `sos/` Splashtop SOS download page.
78
77
  - **404** — otherwise returns a styled `404` echoing the unmatched `host + uri`.
79
- - **Design Demo Admin tool** — `/design` serves `design/index.php`, a self-serve UI for the design team to publish and version Claude Design HTML exports via the GitHub API. It runs as a normal directory request (not through the redirect map). See the [Design Demo Admin](features/design-demo-admin.md) feature doc.
78
+
79
+ > **The Design Demo Admin tool moved out of `forward`.** As of 2026-07-24 the self-serve
80
+ > publishing UI lives in the SSO-protected **Tools** 1.0 app at `/design` (see
81
+ > `1.0/apps/tools/features/design-demo-admin.md`), and `forward/design/` was deleted. `forward`
82
+ > still **stores** the demo data (per-project `project.json` + `vN/index.html`, repo-root
83
+ > `tabs.json`) and **hosts** the demos publicly at `demo.togatech.com` (URLs unchanged); it no
84
+ > longer contains the admin UI. Tools writes into this repo's `_main` over the GitHub API.
80
85
 
81
86
  ## Deployment (Elastic Beanstalk / Apache)
82
87
 
@@ -92,7 +97,7 @@ The `.htaccess` at the repo root drives a plain-text lookup table:
92
97
  - `composer.json` declares a `Togatech\Forward\` PSR-4 autoload over `src/` but has **no
93
98
  dependencies** and no `src/` is shipped today — the app is effectively a single
94
99
  `index.php` plus the Apache config.
95
- - **`DirectoryIndex index.html index.php`** in `.htaccess` makes `/design/` load `design/index.php` and lets nested static-demo version paths `/<project>/<vN>/` serve via Apache's normal directory index. The single-segment static-demo rewrite rule does not match two-segment paths, and the `index.php` map-miss fallback does not fire for real directories — so versioned demo URLs resolve as directory-index lookups.
100
+ - **`DirectoryIndex index.html index.php`** in `.htaccess` lets nested static-demo version paths `/<project>/<vN>/` serve via Apache's normal directory index. The single-segment static-demo rewrite rule does not match two-segment paths, and the `index.php` map-miss fallback does not fire for real directories — so versioned demo URLs resolve as directory-index lookups.
96
101
 
97
102
  ## The `decrypt()` scheme
98
103
 
@@ -123,3 +128,4 @@ Treat this as obfuscation only. Anything genuinely sensitive must not rely on it
123
128
  - 2026-06-18 (jcardinal) — Initial architecture documentation; registered `forward`
124
129
  (Forwarder, standalone) in the knowledge base.
125
130
  - 2026-06-23 (jcardinal) — Added the `/design` Design Demo Admin route and a `DirectoryIndex index.html index.php` directive in `.htaccess` (enables `/design/` and nested `/<project>/<vN>/` demo paths).
131
+ - 2026-07-24 (jcardinal) — Removed the Design Demo Admin tool from `forward` (deleted `forward/design/`); it now lives in the SSO-protected Tools 1.0 app (`1.0/apps/tools/features/design-demo-admin.md`). `forward` still stores and hosts the demo data/`tabs.json`; Tools writes to `_main` over the GitHub API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.433",
3
+ "version": "1.0.435",
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",
@@ -1,202 +0,0 @@
1
- ---
2
- title: Design Demo Admin Tool
3
- framework: "standalone"
4
- repo: forward
5
- project: Forwarder
6
- client: shared
7
- type: feature
8
- status: active
9
- updated: 2026-06-25
10
- owners: ["jcardinal"]
11
- files:
12
- - forward/design/index.php
13
- - forward/tabs.json
14
- - forward/design/.config.example.php
15
- - forward/.gitignore
16
- - forward/.htaccess
17
- - forward/.ebextensions/php-uploads.config
18
- related:
19
- - standalone/apps/forward/features/static-demo-hosting.md
20
- - standalone/apps/forward/architecture.md
21
- ---
22
-
23
- ## Summary
24
-
25
- A standalone, dependency-free PHP tool served at `https://demo.togatech.com/design` that
26
- lets the **design team** publish self-contained "Claude Design" HTML exports to the
27
- `forward` repo as **versioned demos** — no developer, no git, no local checkout required.
28
- It sits on top of [Static Demo Hosting](static-demo-hosting.md): static hosting serves the
29
- files; this tool is the self-serve UI that writes and versions them.
30
-
31
- The tool does **all** repo access over the **GitHub REST API** — never the local
32
- filesystem — because in production it runs on the Elastic Beanstalk host, not a git
33
- checkout. Publishing pushes a commit to `_main`, which triggers the existing EB
34
- auto-deploy ("live in a few minutes").
35
-
36
- ## Key files / entry points
37
-
38
- - `forward/design/index.php` — the entire tool: a single-file PHP backend + HTML/JS UI.
39
- `declare(strict_types=1)`. Served at `/design` (was `/admin`; renamed this session).
40
- - `forward/design/.config.example.php` — template for local dev config (the real
41
- `.config.php` is git-ignored).
42
- - `forward/.gitignore` — ignores `design/.config.php` so a local token is never tracked.
43
- - `forward/.htaccess` — `DirectoryIndex index.html index.php` so `/design/` loads
44
- `index.php`, and so nested `/<project>/<vN>/` paths serve via Apache's directory index.
45
-
46
- ## How it works
47
-
48
- ### GitHub API access (no filesystem)
49
-
50
- - **Reads / listing:** GitHub **Contents API**.
51
- - **Writes:** GitHub **Git Data API** — blob → tree → commit → update-ref — so a publish
52
- is an **atomic multi-file commit** (the version's files + the regenerated stub +
53
- `project.json` all land in one commit).
54
- - **Auth:** token from `getenv('FORWARD_GITHUB_TOKEN')` (an Elastic Beanstalk environment
55
- property in prod) or an uncommitted `design/.config.php` for local dev. See gotchas — the
56
- token must **never** live in the tracked `index.php`.
57
-
58
- ### Versioning model (decided this session)
59
-
60
- - **Sequential `/v1`, `/v2`, `/v3` URL folders** — not semver, not date-based.
61
- - Each project has a **`project.json` manifest** at its folder root:
62
- ```json
63
- {
64
- "title": "...", "latest": "v3", "hidden": false,
65
- "versions": [
66
- { "id": "v3", "label": "...", "notes": "...", "author": "...",
67
- "date": "YYYY-MM-DD", "hidden": false }
68
- ]
69
- }
70
- ```
71
- Human detail (label / author / notes) lives in the **manifest**, not the URL.
72
- - **"latest" pointer:** the bare `/<project>` serves an auto-generated `index.html` **stub**
73
- that meta-refresh / JS-redirects (relative `./vN/`) to the newest **visible** version.
74
- The stub is regenerated on every publish/unlist.
75
- - A directory is treated as a **managed demo iff it contains `project.json`** — this is how
76
- the tool distinguishes demos from infrastructure directories.
77
-
78
- ### Actions
79
-
80
- - **Publish a version** — uploads a self-contained HTML export as the next `vN/index.html`,
81
- appends a version entry to `project.json`, regenerates the stub, commits.
82
- - **Adopt** — converts a legacy `index.html`-only demo folder (e.g. `togadesk`) into a
83
- managed demo: moves the existing page to `v1/index.html`, writes `project.json`,
84
- regenerates the stub.
85
- - **Remove = unlist only** — sets a `hidden` flag in `project.json`; files are **never
86
- deleted** (explicit owner decision).
87
- - **Edit version metadata** (`apiEditVersion`) — an "Edit" button on each version row opens
88
- a modal pre-filled with the version's label, author ("your name"), and notes. Saving
89
- commits an **updated `project.json` only** — the version id and the hosted `vN/index.html`
90
- are unchanged, and the redirect stub does not depend on this metadata, so **no stub
91
- rebuild**. Reuses the existing `isValidVersionId` guard and `postField` length caps (notes
92
- cap 2000).
93
- - **Copy URL** — a button on each project card copies the project's main live URL
94
- (`https://demo.togatech.com/<project>`) to the clipboard via `navigator.clipboard`, with a
95
- brief "Copied ✓" confirmation.
96
- - **Copy Claude Design prompt** — one-click copy of the standard self-contained-HTML export
97
- prompt (the same prompt documented in [static-demo-hosting](static-demo-hosting.md)).
98
- - **Create a tab** (`apiCreateTab`, action `create-tab`) — appends a named tab to
99
- `tabs.json`. Case-insensitive dedupe; name length-capped 40. Commits `tabs.json` only.
100
- - **Move a project to a tab** (`apiSetProjectTab`, action `set-project-tab`) — sets/clears a
101
- project's `tab` field; empty string = Unsorted (the field is `unset`). Commits that
102
- project's `project.json` **only** — tab membership does not affect the redirect stub, so
103
- **no stub rebuild**. `apiCreateProject` also accepts an optional `tab`.
104
-
105
- The full action set is: `list`, `create-project`, `upload`, `adopt`, `set-latest`,
106
- `toggle-version`, `edit-version`, `toggle-project`, `create-tab`, `set-project-tab` (plus
107
- `bootstrap`). `list` also returns the tab list (`{ projects, tabs }`).
108
-
109
- ### Tab-based organization
110
-
111
- Demo projects can be grouped into named **tabs** (e.g. separate Production / Pipeline /
112
- Sandbox).
113
-
114
- - **`tabs.json`** at the repo root holds the ordered tab list — `{"tabs": ["Production",
115
- "Pipeline", "Sandbox"]}`. `loadTabs()` returns those three as **defaults when `tabs.json`
116
- does not exist yet** (so tabs appear before any are created); the file is only written on
117
- the first tab mutation (`create-tab`).
118
- - Each `project.json` gains an optional **`tab`** field (string). Missing/absent =
119
- **"Unsorted"**.
120
- - **UI:** a filter chip bar (All / each tab / Unsorted / + New tab) with per-tab project
121
- counts; clicking a chip filters the list. Each project card has a **Tab `<select>`** to
122
- move the project between tabs (on the select's `change`). The New Project modal has a Tab
123
- dropdown defaulting to the currently-viewed tab.
124
- - The **displayed** tab list is the **union** of `tabs.json` order plus any tab names
125
- referenced by projects — so removing a tab from `tabs.json` never orphans a project.
126
-
127
- ### Naming & validation
128
-
129
- - Folder names auto-normalized to **lowercase kebab-case**, validated against a kebab regex
130
- plus a reserved/excluded list.
131
- - **`EXCLUDED_FOLDERS`** constant (`design`, `sos`) + `RESERVED_NAMES` are never listed or
132
- managed (owner-requested; expected to grow over time).
133
-
134
- ### Hardening
135
-
136
- Input length caps; version-id regex guard; HTML content sniff on upload; security headers;
137
- no client-facing leakage of GitHub/curl error detail; `declare(strict_types=1)`.
138
-
139
- ## Access control
140
-
141
- **No authentication** — explicit owner product decision. The `/design` URL is unlisted;
142
- anyone who reaches it can publish or unlist. Do not treat the tool as protected.
143
-
144
- ## Data model
145
-
146
- `project.json` per managed demo (see Versioning model above), plus an optional `tab` string
147
- field per project (absent = Unsorted). A single repo-root **`tabs.json`** —
148
- `{"tabs": [...]}` — holds the ordered tab list (defaults to `["Production", "Pipeline",
149
- "Sandbox"]` until first written). No database.
150
-
151
- ## Client variations
152
-
153
- None — internal/shared design-team tool.
154
-
155
- ## Gotchas / known issues
156
-
157
- - **Contents API inlines only ≤1 MB.** The GitHub Contents API returns an **empty**
158
- `content` field for files over ~1 MB. Design exports are routinely multi-MB (the
159
- `togadesk` demo is ~4.2 MB), so reading them via the base64 Contents response silently
160
- returned empty content — adopt committed a **0-byte `v1/index.html`** (blank page). Fix:
161
- `ghReadFile()` uses the **raw media type** (`Accept: application/vnd.github.raw`), which
162
- streams files up to 100 MB.
163
- - **Never hardcode the GitHub token in the tracked `index.php`.** `design/index.php` is
164
- git-**tracked**; only `design/.config.php` is git-ignored. Hardcoding a `ghp_` token in
165
- `index.php` leaked a live token into pushed `_main` history of `agilantsolutions/forward`.
166
- Use `getenv('FORWARD_GITHUB_TOKEN')` (EB env property) or the uncommitted `.config.php`. A
167
- leaked token must be **revoked/rotated** — a later commit does not remove it from history.
168
- Prefer a **fine-grained PAT** scoped to only the `forward` repo's Contents R/W.
169
- - **Raise PHP upload limits for multi-MB designs (AL1 / PHP 7.3 specifics).** Default
170
- `upload_max_filesize` (~2 MB) rejects real multi-MB design exports. The deploy platform
171
- is **PHP 7.3 on 64-bit Amazon Linux 1**, where these values are **not** settable via the
172
- `aws:elasticbeanstalk:container:php:phpini` option namespace. Instead a
173
- `.ebextensions/php-uploads.config` drops a custom ini at
174
- `/etc/php-7.3.d/99-design-uploads.ini` setting `upload_max_filesize=25M`,
175
- `post_max_size=30M`, `memory_limit=256M`, `max_execution_time=60`.
176
- - **Publishing pushes to `_main`** and triggers EB auto-deploy — changes go live in a few
177
- minutes; there is no staging step.
178
-
179
- ## Change history
180
-
181
- - 2026-06-23 — Built the Design Demo Admin tool: GitHub-API-only publishing, sequential
182
- `/vN` versioning with a `project.json` manifest + auto-generated latest-pointer stub,
183
- adopt/unlist actions, `/design` route. Fixed the >1 MB Contents-API empty-content bug
184
- (raw media type) and the tracked-token leak (env/`.config.php`). (jcardinal)
185
- - 2026-06-24 — Added **edit-version** (`apiEditVersion`): an Edit button + modal to revise a
186
- version's label/author/notes, committing `project.json` only (no stub rebuild; reuses
187
- `isValidVersionId` + length caps). Added a **Copy URL** button on each project card
188
- (`navigator.clipboard` → `https://demo.togatech.com/<project>`). Both additive; no new
189
- security surface. (jcardinal)
190
- - 2026-06-25 — Added **tab-based organization**: new repo-root `tabs.json` (ordered tab
191
- list, defaults Production/Pipeline/Sandbox until first written via `create-tab`), optional
192
- `tab` field on `project.json` (absent = Unsorted), and actions **`create-tab`** /
193
- **`set-project-tab`** (`apiCreateProject` accepts an optional `tab`). UI filter chip bar
194
- with per-tab counts, per-card Tab select, and a Tab dropdown on the New Project modal. The
195
- displayed tab list is the union of `tabs.json` and tabs referenced by projects, so dropping
196
- a tab from `tabs.json` never orphans a project. `set-project-tab` commits `project.json`
197
- only (no stub rebuild). (jcardinal)
198
-
199
- ## Related docs
200
-
201
- - [Static Demo Hosting](static-demo-hosting.md)
202
- - [Forwarder Architecture](../architecture.md)