toga-ai 1.0.908 → 1.0.910
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/2.0/apps/toga-blox/features/table.md +10 -3
- package/knowledge/2.0/apps/toga-blox/workflows/dynamic-publish-pipeline.md +7 -1
- package/knowledge/2.0/apps/worker2/architecture.md +3 -1
- package/knowledge/2.0/apps/worker2/features/cross-account-aws-access.md +16 -1
- package/knowledge/2.0/apps/worker2/features/tracking-status-refresh.md +4 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [apeterson, tcox, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/Table/hooks/useFetchTablePageMeta.ts
|
|
@@ -85,10 +85,10 @@ Per-column header controls, each a popover trigger with `data-active` / `data-op
|
|
|
85
85
|
|
|
86
86
|
- **`HeaderFilterSearch`** — text filter, **multi-chip with per-chip mode**. Each confirmed search becomes its own `TextFilterTag = { value, mode }` (mode ∈ Includes / Starts with / Ends with / Exactly / Excludes). The mode dropdown sets the mode for the **next** chip added; existing chips keep the mode they were created with. All chips on a column are combined with **AND**. `onFilterChange` emits the full tags array (`{ tags }`); the component rehydrates every chip from props and renders each as "Mode: value". `isActive = tags.length > 0`. The mode dropdown is portalled to `document.body` with fixed positioning recomputed on scroll. The search icon (solid when active, regular otherwise) is hidden while the input is focused; both the trigger button and the icon span carry `data-active`. The clear button must not widen the input row (fixed-width menu). (Previously a column allowed only one term / one mode — a second search replaced the first and switching mode re-applied it to everything.)
|
|
87
87
|
- **`HeaderFilterRange`** — numeric, modes exactly/moreThan/lessThan + a range toggle (min/max); emits `{ key, value }[]` (`eq`/`min`/`max`/`between`). Reads its persisted operator/values back from a `filterParams` prop (not `filterValue`, which collapses to the literal `"active"` when a range filter is set); `PrimaryTableHeaderCell` must pass `filterParams` through.
|
|
88
|
-
- **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs.
|
|
88
|
+
- **`HeaderFilterMultiselect`** — checkbox list with "Select All"; emits comma-joined slugs. **Its "Clear" must call the `onClear` prop, not just reset local state** — see the clear-contract gotcha below.
|
|
89
89
|
- **`HeaderFilterDate`** — calendar picker, modes exactly/before/after + range; converts `MM/DD/YYYY` ↔ URL `YYYY-MM-DD`; calendar + mode dropdown both portalled.
|
|
90
90
|
- **`HeaderFilterButton`** — generic trigger: shows a numbered badge when active with a `filterIndex`, otherwise the search icon (`data-active`).
|
|
91
|
-
- **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys off current sort direction.
|
|
91
|
+
- **`SortIcon`** — asc/desc/reset menu with a multi-sort index badge; button visibility keys off current sort direction. The badge **points down for `desc`** via a `.sortDropdownBadgeDesc` modifier that rotates the whole badge 180deg, with a nested rule counter-rotating `.sortDropdownBadgeIndex` so the sort-position digit still reads upright. **Rotate, never swap the icon** — FontAwesome Pro has no down-pointing `triangle`, and `caret`/`sort` have a different silhouette, so a swap changes the designed shape. The offset is held in one CSS variable both rules read (`--sortDropdownBadgeIndex-offset`) so retuning the base offset cannot drift the rotated copy. These rotations are deliberately **not** host tokens: a 180deg flip is geometry, not brand — same treatment as the un-tokenized `translateX(-50%)` centering on `.sortDropdownMenu` (a CSS comment records this so it is not "fixed" later). (Before 2026-09-28 the badge always rendered the solid `triangle` and ignored `isSorted`, so asc and desc looked identical.)
|
|
92
92
|
|
|
93
93
|
### Hooks (`hooks/`)
|
|
94
94
|
|
|
@@ -118,6 +118,10 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
118
118
|
|
|
119
119
|
## Gotchas
|
|
120
120
|
|
|
121
|
+
- **A filter popover's "Clear" must call `onClear()` — resetting local state only leaves the table filtered.** `HeaderFilterMultiselect.handleClear` did just `setSelectedOptions([])`, so the URL param (owned by the consuming app) and the applied filter survived: the popover showed nothing selected while the table stayed filtered. Fixed 2026-09-28 — it now calls `onClear()` and closes, **guarded by `if (filterValue)`**. The guard matters: `onClear` routes through `PrimaryTableHeaderCell` → `table.options.onColumnFiltersChange` → toga25-supply `useServerTableUrlState`, which resets to **page 1 on any filter change** — so an unguarded Clear with nothing applied would bounce the user off their page.
|
|
122
|
+
- **⚠ OPEN — `HeaderFilterRange.handleClear` has the same bug** (~line 162): it resets `singleValue`/`minValue`/`maxValue` but never calls `onClear()` and never closes the popover. Same fix shape as the multi-select. Not fixed as of 2026-09-28.
|
|
123
|
+
- **⚠ OPEN — `SortIcon` has no `aria-*` at all.** Sort direction and position are conveyed by shape only — no `aria-label`, no `aria-sort`. Pre-existing; needs its own ticket.
|
|
124
|
+
- **A unit test cannot prove a CSS change — verify in blox's own Storybook.** JSDOM applies no real layout, so a Vitest test can only assert the modifier class is present; it can never show the triangle actually flipped or that the digit stayed upright. Add a co-located `*.stories.tsx` rendering the states side by side, run `npm run blox` (port 6006), and look at it.
|
|
121
125
|
- **⚠ OPEN — the multi-select filter matches by SUBSTRING, not exactly.** `HeaderFilterMultiselect` comma-joins the picked slugs and `src/api/tableData/getDataTableData.ts` turns that into `where: { or: [ { field: { contains: value } }, … ] }`. Picking "Jon Smith" also matches "Jon Smithson" — it over-includes rows.
|
|
122
126
|
- **⚠ OPEN — an option value containing a comma silently returns zero rows.** It splits into two bogus filter terms, with no error. Both bugs are pre-existing and affect **every** multi-select column including Order Status; both fix in one place — send a list / exact-equals operator instead of a comma-joined `contains`. Not fixed as of 2026-09-15.
|
|
123
127
|
- **⚠ `useTableData` APPENDS `.uuid` to `additionalData.slug` — unless the slug is on a hardcoded allow-list.** The list is exactly `catalogId`, `customerId`, `Items.catalogId`, `SalesOrderItems.uuid`, `SalesOrders.uuid` (verified in `@agilant/toga-blox` **1.1.2-production.144**, the version toga25-supply pins). Consequences for anyone writing a filter slug into config or a `TableLevel` callback:
|
|
@@ -139,6 +143,9 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
139
143
|
- **Filter presentational components must seed their internal state from props.** Filter state survives the URL round-trip and is rehydrated into TanStack `columnFilters` by toga25-supply `useServerTableUrlState`, carried through `PrimaryTableHeaderCell`. But `HeaderFilterSearch`/`HeaderFilterRange` are remounted fresh each time a popover opens, so they must seed local state from props or reopening *shows the wrong state*. **Text (`HeaderFilterSearch`)** now persists as a single readable URL param `<slug>_<col>_filter` = comma-separated `mode:value` pairs (values URL-encoded so `:`/`,` inside a term can't corrupt the delimiters); `getDataTableData` parses it and pushes **one AND WHERE condition per chip** (mode→op: includes→contains, startsWith→starts, endsWith→ends, exactly→eq, excludes→excludes). The component rehydrates the full `tags` array from props. Old base64/legacy single-mode `_starts`/`_ends`/… URLs don't parse under the new scheme (graceful — just re-apply the filter). The browser may percent-encode `:`/`,` in the address bar but it round-trips. **Range (`HeaderFilterRange`)** still persists as key suffixes (`_min`/`_max`/`_eq`/`_between`); it reads mode + value(s) back from `filterParams` via a rehydration effect keyed on `JSON.stringify(filterParams)` (`eq`→Exactly, `min`→MoreThan, `max`→LessThan, `between`→range min/max).
|
|
140
144
|
- **Table/surface meta is cached with `Infinity` staleTime/gcTime** (`useFetchTablePageMeta`), so after a DB metadata change (`TableViewFields`, `Core.RecordFields`) a **hard reload** is required to see it. And the app consumes toga-blox's built **`dist/`**, not `src/` — editing blox source requires `npm run build` before it takes effect at runtime.
|
|
141
145
|
|
|
146
|
+
## Change history
|
|
147
|
+
- 2026-09-28 — multi-select filter "Clear" now actually clears (calls `onClear`, page-1 guard); sort badge now points down for `desc` via CSS rotation (apeterson)
|
|
148
|
+
|
|
142
149
|
## Related
|
|
143
150
|
|
|
144
151
|
- [Primary Table templates](primary-table-templates.md) — the wired table built on this.
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [jcardinal, apeterson, tcox, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/.github/workflows/publish.yml
|
|
@@ -110,6 +110,9 @@ Dates: the **August 2026** npm change removed 2FA-bypass for *account and packag
|
|
|
110
110
|
If any one fails, bump on the feature branch instead. Note the bump commit lands *only* on `_sandbox-client` and is never merged back, which is precisely what generates the "N behind" release plumbing.
|
|
111
111
|
- **`gh` is not installed on every dev machine.** The skill's `gh run watch` monitoring step simply does not work there. Fall back to polling the registry: `npm view @agilant/toga-blox@sandbox-client version` (or `npm dist-tag ls @agilant/toga-blox`) until the new version appears — the registry is the authoritative confirmation anyway.
|
|
112
112
|
- **A publish is only half the deploy — and the consumer must depend on the LITERAL dist-tag.** In `toga25-supply/package.json` the dependency is the bare tag string (`"@agilant/toga-blox": "sandbox-client"`), **not** a version range. `npm install @agilant/toga-blox@sandbox-client` rewrites it to a `^` range, which then stops following base bumps — re-set the literal tag, re-run `npm install`, commit `package-lock.json`. Verify what is installed with `node -p "require('./node_modules/@agilant/toga-blox/package.json').version"`. Exact pins drift: branch `TRUE-81379` was pinned to `1.1.3-production.166` while `node_modules` held `1.1.10-sandbox-client.163` — check this first when a fix "works locally but not after deploy". Likewise the feature branch keeps its old base version after a bump lands on `_sandbox-client`; that is expected.
|
|
113
|
+
- **⚠ A CARET on a prerelease pin can NEVER upgrade — it reads as "my fix did not deploy", not as drift.** npm matches `^` on a prerelease only inside the **same** `major.minor.patch`, so `toga25-supply` `_sandbox-client` pinned at `"^1.1.4-sandbox-client.188"` could not pick up `1.1.10-sandbox-client.191` — the bump had to be explicit. This is the concrete failure mode behind the literal-tag rule above: silent never-upgrade, not slow drift. Confirmed 2026-09-28.
|
|
114
|
+
- **`toga25-supply` `_sandbox-client` has NO `amplify.yml`** — it builds from the Amplify **console** build spec. If that spec runs `npm install "@agilant/toga-blox@$MODE"`, the deployed build takes the **channel head** and the `package.json` pin governs local dev only. Check the console spec before chasing a "stale deploy".
|
|
115
|
+
- **Confirm a bump landed by grepping the INSTALLED `dist/`, not by trusting the install.** e.g. `grep -n "sortDropdownBadgeDesc" node_modules/@agilant/toga-blox/dist/components/Table/components/columnFiltersAndSorts/SortIcon.js`. Same technique proves a published channel actually carries a fix (2026-09-28: verified a bug was live in `1.1.4-production.184`'s dist, not just in src).
|
|
113
116
|
- **⚠ Credentials have been found in the local git remote URL.** Verified 2026-08-26: the `origin` remote for the `toga-blox-npm` checkout had a **GitHub PAT embedded in plaintext in the URL** (visible to anything that runs `git remote get-url origin`, and to any tool that reads `.git/config`). Check for this and re-point the remote at SSH or a credential helper; treat any token found this way as compromised and rotate it. This is the local-config sibling of the committed-`.npmrc` violation already recorded in `../../../standards/frontend.md`.
|
|
114
117
|
- **FontAwesome v7 `style`-prop TS2322** (fixed 2026-07-21 in `getFontAwesomeIcon.tsx`, and a reusable pattern for any consumer). FA v7 types the `FontAwesomeIcon` `style` prop as `CSSProperties & CSSVariables`, where `CSSVariables` requires a `--fa-*` custom-property index signature that plain `React.CSSProperties` lacks. Cast the passed style to `React.CSSProperties & Record<\`--${string}\`, string>` — the value type must be `string` (**not** `string | number`; `CSSVariables` requires `string`). Runtime and function signature are unchanged, so it is safe for the ~40 consumers.
|
|
115
118
|
- **The `_production`/`_beta` build copies only `.scss` — a plain `.css` component stylesheet is silently dropped from the tarball.** On `_production` and `_beta` the `build` script is `tsc && copyfiles -u 1 "src/**/*.scss" dist`: `tsc` compiles a component's `import "./Foo.css"` into the emitted JS, but `copyfiles` never ships the `.css`, so the published package contains JS importing a file that isn't in the tarball. Consumers then fail their Vite/Rollup build with `Could not resolve "./Foo.css"` (seen with `EnvironmentBadge` in `@agilant/toga-blox@beta` 1.0.316-beta.108, breaking `toga2-commerce`). **Convention on the prod/beta build: author component stylesheets as `.scss`, never `.css`** (a flat `.css` file is valid SCSS as-is, so renaming is the zero-risk fix — no build-script change). Note the build description in `architecture.md` (copies `*.scss/*.css/*.module.css`) reflects the in-progress `feature-new-table` branch, **not** the current `_production`/`_beta` build; that feature branch is not to be merged to prod/beta just to pick up the wider copy glob.
|
|
@@ -120,6 +123,9 @@ Dates: the **August 2026** npm change removed 2FA-bypass for *account and packag
|
|
|
120
123
|
- **Keep the production base ahead of, or level with, sandbox.** `_production`'s base had drifted **12 patches behind** `_sandbox-client`, so the production channel was publishing *numerically lower* versions than sandbox. The base was bumped `1.0.322` → **1.1.0** to mark the `feature-new-table` release and stop that inversion; CI published `1.1.1-production.139`.
|
|
121
124
|
- **Pushing to a `_*` branch IS the publish — there is no separate step to run afterwards.** The merge push itself triggered a publish (`1.0.323-production.137`) about a minute after the merge landed. Consequence: a merge that is not release-ready still ships. Get the tree right *before* you push to a release branch.
|
|
122
125
|
|
|
126
|
+
## Change history
|
|
127
|
+
- 2026-09-28 — recorded the caret-on-prerelease never-upgrade trap, toga25-supply's missing `amplify.yml`, and dist-grep verification (apeterson)
|
|
128
|
+
|
|
123
129
|
## Related
|
|
124
130
|
|
|
125
131
|
- [Landing a large feature branch](./landing-a-large-feature-branch.md) — the merge procedure that feeds a release branch.
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [jcardinal, ajean, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Controller/Index.php
|
|
@@ -151,6 +151,8 @@ For **programmatic** enqueue via `_Worker::runTask()`, the MySQL-first rule was
|
|
|
151
151
|
|
|
152
152
|
## Gotchas
|
|
153
153
|
|
|
154
|
+
- **Shrinking `WorkerJobs.output` MEDIUMTEXT → TEXT (planned, not yet done).** TEXT holds 65,535 **bytes** (utf8mb4 = up to 4 bytes/char), not chars. Trim byte-safe before the ALTER, repeating until 0 rows: `UPDATE WorkerJobs SET output = LEFT(output, CHAR_LENGTH(output) - (LENGTH(output) - 65535)) WHERE LENGTH(output) > 65535 LIMIT 1000`. After the change, every producer must truncate first or the result UPDATE fails with 1406 Data too long (strict mode) and the job result is lost: PHP check-out (both paths in `Controller/Index.php`) and the JobScheduler watchdog Lambda UPDATE (repackage + redeploy it). The `MODIFY` must restate the column COMMENT. On MySQL 8 the type change rebuilds the table.
|
|
155
|
+
|
|
154
156
|
- **⚠ OBSERVED `Core.WorkerJobs` retention is ~30 days, not 90 — unexplained.** Measured 2026-09-15 in prod: `MIN(dtCreated)` across the **whole** table is **2026-08-16 02:00:46** (790,597 rows). Either the cleanup cron does not behave as described, or something else prunes the table. **Not investigated** — no cause is asserted.
|
|
155
157
|
- **A missing `WorkerJobs` row is NOT proof the job never ran.** Any "there is no job for this record, so the webhook never fired" conclusion is invalid past the ~30-day floor. That exact wrong call was made and retracted on 2026-09-15. Same failure family as *an empty log table is not proof a code path never ran*.
|
|
156
158
|
- **The watchdog does not stop the PHP process** — an over-budget action keeps running past a false failure row and typically loses its result UPDATE to MySQL 2006 (prod job 1075223). See JobScheduler Part B above.
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-28
|
|
10
10
|
owners: [jcardinal, bala]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Component/Aws/Workloads/Workloads.php
|
|
@@ -82,6 +82,21 @@ Each `WorkloadsRuntime` role has broad service permissions (`elasticbeanstalk:*`
|
|
|
82
82
|
|
|
83
83
|
The helper adoption was captured earlier: the prior inline STS/credential code and the `HEALTH_ASSUME_ROLE_NAME` / `HEALTH_ASSUME_ROLE_SESSION` constants were removed in favor of `_Component_Aws_Workloads` + the `[cloud]` config keys. The prior `CheckBeanstalks()` action was renamed `ElasticBeanstalkInstances()` (dbchanges2 `Core/2026-08-10c` updates the CronJobs action name; `Core/2026-08-10d` seeds the parameterized `ElasticBeanstalkHealth` cron row, `isActive=0`, and carries the `environments`-map sample parameters).
|
|
84
84
|
|
|
85
|
+
### Second consumer — ElasticBeanstalkInstances (auto-terminate stuck instances)
|
|
86
|
+
|
|
87
|
+
`_Worker_Infrastructure_CloudWatch::ElasticBeanstalkInstances(?regions)` — prod `Core.CronJobs` id 39, every 10 min. Rewritten 2026-09-28 (not yet verified by a real prod run).
|
|
88
|
+
- **Why:** the old version used the aws CLI via `_Cloud::$preCommands` + the legacy static `[cloud]` key, which cannot see 868's EB environments → failed **100% of runs** since 2026-08-29 (`CloudWatch: No environments returned for region us-east-1`). Auto-terminate never worked.
|
|
89
|
+
- **Now:** `assumeCredentials()` once, SDK `ElasticBeanstalkClient` + `Ec2Client` per region (same shape as ElasticBeanstalkHealth).
|
|
90
|
+
- Account is const `INSTANCES_AWS_ACCOUNT_ID = 654654170868`, **not overridable** (it terminates). Optional `regions` must be in `INSTANCES_ALLOWED_REGIONS` (us-east-1, us-west-2, eu-west-1) — region goes into the SDK endpoint host, so allowlist it.
|
|
91
|
+
- Returns JSON `{isSuccess, awsAccountId, environmentCount, terminatedCount, alarmCount, errorCount, terminated[], alarms[], errors[], environments[]}`. Throws only on: region not allowed, assume-role fails, or no region readable. Unhealthy instances no longer throw (developer choice).
|
|
92
|
+
- Guards: 240s budget (`INSTANCES_TIME_BUDGET_SECONDS`; unreached envs go to `errors`); max 3 terminations per run; max floor(50%) of an env's instances but always at least 1 (a single-instance env's only instance may be terminated — EB replaces it; developer choice). Capped / over-budget items go to `errors`, never silently skipped.
|
|
93
|
+
- Termination email kept (private `notifyInstanceTerminated` → Notification/EmailTemplate/Send).
|
|
94
|
+
- `_Cloud::terminateInstance()` in `_underscore` now has no known callers in worker2/_underscore (left in place).
|
|
95
|
+
|
|
96
|
+
### Terminate guard on 868 WorkloadsRuntime
|
|
97
|
+
|
|
98
|
+
Applied 2026-09-28 (AWS CLI) to inline policy `WorkloadsRuntimePermissions` in 654654170868: statement `DenyTerminateNonBeanstalk` — Deny `ec2:TerminateInstances` on `*` when `aws:ResourceTag/elasticbeanstalk:environment-name` is `Null`. Any caller of WorkloadsRuntime can terminate **only EB-managed instances**. The other two accounts' roles do not have this statement.
|
|
99
|
+
|
|
85
100
|
### Legacy static key — being retired
|
|
86
101
|
|
|
87
102
|
The legacy `[cloud]` static access key is a **personal IAM user in account 502614707982**. Being retired for new code but **still used by older code paths** — ZoomInfo, VipSupport, and the `_Cloud` CLI path. **Retrofit of that existing code is deliberately deferred** (developer decision, this session): document the new approach; do not migrate old code yet. New code must not adopt the static key.
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-09-28
|
|
10
|
+
owners: ["bala", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Sync/TrackingNumbers.php
|
|
13
13
|
- _underscore/Component/Library/Carriers/Fedex/Fedex.php
|
|
@@ -156,6 +156,8 @@ bookkeeping columns.
|
|
|
156
156
|
|
|
157
157
|
## Gotchas
|
|
158
158
|
|
|
159
|
+
- **Every `c_` refresh column must exist in every `Client_*` DB.** One missing column fails that client's `RefreshClient` every run: Adyen failed 144 runs (2026-09-21 14:00 → 2026-09-22 13:50) on `Unknown column 'TrackingNumbers.c_trackingCheckFailures'` until the column was added. Check the fan-out when a new client is provisioned.
|
|
160
|
+
|
|
159
161
|
### ⚠ GO-LIVE GATE — worker2 → api2 hard-crashes on PHP 8.5
|
|
160
162
|
Writes go through api2 PUT, and a worker2 → api2 call **hard-crashes the request on PHP 8.5 (OpenSSL
|
|
161
163
|
3.5.5): a process-level 500 with no catchable trace, and the shutdown handler never fires.** The prod
|
package/package.json
CHANGED