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.
- package/knowledge/1.0/apps/tools/INDEX.md +1 -0
- package/knowledge/1.0/apps/tools/architecture.md +10 -0
- package/knowledge/1.0/apps/tools/features/design-demo-admin.md +171 -0
- package/knowledge/1.0/apps/tools/workflows/deploy-to-elastic-beanstalk-al2023.md +12 -1
- package/knowledge/1.0/standards/framework-rules.md +14 -0
- package/knowledge/2.0/apps/worker2/features/team-sprint-management.md +117 -2
- package/knowledge/INDEX.md +2 -2
- package/knowledge/standalone/apps/forward/INDEX.md +1 -2
- package/knowledge/standalone/apps/forward/architecture.md +9 -3
- package/package.json +1 -1
- package/knowledge/standalone/apps/forward/features/design-demo-admin.md +0 -202
|
@@ -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
|
|
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-
|
|
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
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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) —
|
|
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) —
|
|
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/
|
|
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
|
-
|
|
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`
|
|
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,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)
|