toga-ai 1.0.909 → 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.
|
@@ -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.
|
package/package.json
CHANGED