toga-ai 1.0.404 → 1.0.406

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.
@@ -0,0 +1,58 @@
1
+ ---
2
+ type: session
3
+ slug: deployment-logout-guard
4
+ title: Force-logout-on-deployment guard for toga25-supply
5
+ author: apeterson
6
+ repos: [toga25-supply]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-07-21
11
+ updated: 2026-07-21
12
+ ---
13
+
14
+ # Session: deployment-logout-guard
15
+ **Date:** 2026-07-21
16
+ **Project/Repo:** toga25-supply (2.0)
17
+ **Task:** Port toga2-commerce's force-logout-on-core-parameter-change mechanism into toga25-supply so a large deployment forces all users to sign out and fully reset session/localStorage/React Query cache — with a smooth UX.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+ <!-- Include specific file paths and evidence -->
23
+ - Reviewed the reference mechanism in `toga2-commerce/src/contexts/AuthContext.tsx`: polls Core parameter `META_LAST_REFRESH_DATETIME` via `/core-parameters`, stores the login-time value as a baseline, and on any later differing fetch calls `logout()` + hard-redirects to `/`.
24
+ - Created `toga25-supply/src/hooks/useDeploymentGuard.tsx` — a `useQuery` (enabled while authenticated, `staleTime:0`, `refetchOnWindowFocus:true`, `refetchInterval` 5min) reading `apiGet("/core-parameters", { fields:["value"], key:"META_LAST_REFRESH_DATETIME" })` → `response.data.coreParameters` (array-or-object tolerant). Baseline stored in localStorage key `deployment-version`; first observation sets it (no logout), a later mismatch triggers reset. Exports render-nothing `<DeploymentGuard />`.
25
+ - Smooth UX: on mismatch shows a non-dismissing info toast ("A new version is available — signing you out briefly…") and waits 2.5s before calling `logout()`.
26
+ - Wired `<DeploymentGuard />` into `toga25-supply/src/App.tsx` inside the `AuthProvider`/`ToasterProvider`/`ThemeProvider` tree (both `useAuth` and `useToaster` resolve).
27
+ - Confirmed the existing `AuthContext.logout()` (lines 80–90) already does `performLogout()` + `clearUser()` + `localStorage.clear()` + `queryClient.clear()` — so the entire localStorage (tokens, `zu-user`, `zu-hostname`, persisted `supply-chain-query-cache`, and the `deployment-version` baseline) is wiped. User confirmed this full-clear behavior is what they want.
28
+ - `npx tsc --noEmit` — no errors in the changed files (`useDeploymentGuard.tsx`, `App.tsx`).
29
+ - Captured to team KB: new doc `knowledge/2.0/apps/toga25-supply/features/force-logout-on-deployment.md`, PUSHED to `_main`.
30
+
31
+ ## What did NOT work — DO NOT RETRY THESE
32
+ <!-- Exact failure reasons — do not vague-ify -->
33
+ - (none — no failed approaches this session.)
34
+
35
+ ## Not tried yet (candidates for next session)
36
+ - Live verification against a real API response that `response.data.coreParameters[0].value` is populated for this client, and that the `key` shorthand option (vs a `where` clause) is honored by this environment's `/core-parameters` endpoint. If `key` is not supported, switch the fetch to `where: { and: [{ "CoreParameters.key": { "=": "META_LAST_REFRESH_DATETIME" } }] }`.
37
+ - End-to-end manual test: log in, bump `META_LAST_REFRESH_DATETIME` server-side, confirm the toast appears and the user is signed out + everything reset, and that re-login re-establishes the baseline without a logout loop.
38
+
39
+ ## Current file state
40
+ | File | Status | Notes |
41
+ |------|--------|-------|
42
+ | `toga25-supply/src/hooks/useDeploymentGuard.tsx` | Created | Deployment watch hook + `DeploymentGuard` component. Complete, type-checks clean. |
43
+ | `toga25-supply/src/App.tsx` | Modified | Added import of `DeploymentGuard` and rendered `<DeploymentGuard />` above `<AppRouter />` inside the provider tree. |
44
+ | `toga-tech/knowledge/2.0/apps/toga25-supply/features/force-logout-on-deployment.md` | Created (pushed) | Feature doc, owner `apeterson`, pushed to `_main`. |
45
+
46
+ ## Decisions made
47
+ - **Reuse the centralized `logout()` rather than a bespoke reset path.** It already clears all localStorage + React Query cache + redirects, matching the "reset everything" intent. Rejected: writing a separate partial cache-clear (wouldn't reset session as required).
48
+ - **Do NOT preserve the baseline across logout (simpler than commerce).** Because `logout()` clears the baseline and hard-reloads, the next login's first fetch re-establishes it and the mismatch check is skipped when no baseline exists → no logout loop. Rejected commerce's write-new-value-before-logout dance as unnecessary here.
49
+ - **2.5s toast delay before logout** for a smooth, explained sign-out instead of an abrupt kick. Rejected instant logout (jarring) and history-patching navigation refetch from commerce (fragile) in favor of `refetchOnWindowFocus` + 5min interval.
50
+
51
+ ## Blockers
52
+ none
53
+
54
+ ## Exact next step
55
+ > Manually verify against a live/beta API: log in to toga25-supply, confirm the first `/core-parameters` fetch populates `response.data.coreParameters[].value`; if the `key` shorthand returns nothing, change the `fetchDeploymentVersion` options in `src/hooks/useDeploymentGuard.tsx` to a `where` clause on `CoreParameters.key`.
56
+
57
+ ---
58
+ _Saved by /session-save on 2026-07-21_
@@ -0,0 +1,68 @@
1
+ ---
2
+ type: session
3
+ slug: inventory-see-units-inline-chevron
4
+ title: Inventory nested-table "See units" chevron + row-click modal
5
+ author: apeterson
6
+ repos: [toga25-supply, dbchanges2]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-07-21
11
+ updated: 2026-07-21
12
+ ---
13
+
14
+ # Session: inventory-see-units-inline-chevron
15
+ **Date:** 2026-07-21
16
+ **Project/Repo:** toga25-supply (2.0) + dbchanges2
17
+ **Task:** On the Inventory page's nested tables (GenericNestedTables), add a "See units" chevron to the tier-2 (swap) level that swaps to tier-3 units, make a plain row click open the item-record modal instead of swapping, and (remaining work) render that chevron INSIDE the first column's cell content rather than as its own column.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+ <!-- Include specific file paths and evidence -->
23
+ - **Decoupled swap-trigger from row click.** Added optional `buildSwapTriggerColumns(triggerSwap)` to `TableLevel` in `toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx`. When present, the whole-row swap (`getRowHref`) is disabled and inline child expansion is suppressed while nested, so a row click is free to open `renderModal`. Hydrator (`useInventoryPageViewModel.tsx`, role "swap") wires `buildSwapTriggerColumns: makeSeeUnitsColumns` + `renderModal` from `modalKey`. UX confirmed: chevron → tier 3, row click → modal.
24
+ - **Fixed the swap/modal URL-param collision.** Root cause: `src/hooks/useServerTableUrlState.ts` stores the active-row/modal uuid under a param named EXACTLY the table `slug` (`activeRowUuid = searchParams.get(slug)`; `handleRowClick` sets `slug`). The grouping JSON set each swap level's `urlParamKey` = its `fetchSlug`, so a row click wrote `?<slug>=<uuid>` which GenericNestedTables read as "swap active" → jumped to tier 3. Fix: prefixed the swap urlParamKeys with `swap-` in BOTH `FIELDS/DEFAULT/inventoryGroupings.json` and `FIELDS/NYCHH/inventoryGroupings.json` (`items-for-purchase-orders` → `swap-items-for-purchase-orders`; `purchase-orders_items` → `swap-purchase-orders_items`). A `<slug>_...` SUFFIX is NOT usable (the columnFilters parser treats any `${slug}_*` key as a column filter) — must be a PREFIX. Added a doc comment on `TableLevel.urlParamKey`.
25
+ - **Item-record modal opens the catalog Item uuid (was 404 EV-6).** The `items-for-purchase-orders` rows are sales-order lines (row.uuid = SalesOrderItems.uuid), not catalog Items, so `/items?uuid=<lineUuid>` returned 404. Added optional `getModalUuid(row)` on `TableLevel`; `GenericTableLayout.tsx` wraps `handleRowClick` to store `getModalUuid(row) ?? row.uuid`. Hydrator sets `getModalUuid: (row) => row?.Items?.uuid` for the itemRecord swap level. Confirmed working after the DB change below.
26
+ - **DB: hidden `Items.uuid` added to the table view.** `dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql` — idempotent INSERT into `TableViewFields` (slug `itemUuid`, `isVisible=0`, `recordFieldId=106` = Core.RecordFields Items.uuid, tableViewJoin reused from the existing hidden `inventorytype` Items-join field, resolved by slug so it's portable). User ran it on `Client_Nychh` → row now carries `row.Items.uuid`, modal loads (200). Verified via mysql: Core.RecordFields id 106 = Items.uuid; record 21 = Items; tableViewJoinId 125; tableViewId 16 (Client_Nychh).
27
+ - **"Show more" ExpandableCell now renders in nested tables.** `ExpandableCell` only truncates when `tableViewField.isExpandableCell` is true (maxLength 40). The nested view model never marked it. Added `markExpandableCells(withLabels, ["description"])` in `useGenericTableViewModel.tsx` (and fixed a latent early-return that skipped marking when a table had no page-field label overrides). `tsc` clean.
28
+
29
+ ## What did NOT work — DO NOT RETRY THESE
30
+ <!-- Exact failure reasons — do not vague-ify -->
31
+ - **Action column at `position: "start"`** — renders the chevron in a leading gutter column, not adjacent to the cell text. User rejected: wants it to the right of the cell contents.
32
+ - **Action column at `position: 1` (separate column after col 0)** — still a distinct column, not inside the cell. User rejected: "it should be in the actual cell contents, not a new column."
33
+ - **`columnWidths={{ description: 320 }}` in `GenericTableLayout.tsx`** — made it WORSE. 320 also becomes the column `minSize` (per toga-blox `buildTanstackColumns`: assigned width → minSize), forcing the column NARROWER than it had been, so the 40-char preview + "Show more" + copy button no longer fit and "Show more" clipped to "show mo". Current value bumped to 420 as a stopgap (see below) but this whole width-hack approach is being replaced by the in-cell render and should be REMOVED.
34
+ - **Trying to inject the chevron into a cell purely from the app** — not possible. toga-blox owns cell rendering (`resolveCellType`/`buildTanstackColumns` inside `useTableSetup`); there is NO app-level hook to customize a single cell's render. Confirmed by reading the toga-blox dist. Any true in-cell solution REQUIRES a small toga-blox change.
35
+
36
+ ## Not tried yet (candidates for next session)
37
+ - **THE PLANNED APPROACH — in-cell "See units" chevron via a toga-blox cell-suffix slot, targeted by "tier-2 first column":**
38
+ 1. **toga-blox (`~/Agilant/toga-blox-npm`):** `resolveCellType(value, field, …, cellSuffix?)` renders `{content}{cellSuffix}` as a sibling inside its existing flex wrapper (`cellWrapperVariants` = "flex items-center justify-start"), so the chevron sits right after the content, centered, in the same cell — composes with ExpandableCell/CopyableCell for free. `buildTanstackColumns` accepts `cellSuffixByField?: Record<slug, (row)=>ReactNode>` and, in its `cell` fn (already has `row`), passes `cellSuffixByField[field.slug]?.(row.original)` to `resolveCellType`. Thread `cellSuffixByField` through `useTableSetup` → `PrimaryTable*` props.
39
+ 2. **Publish** toga-blox under the `_sandbox-dev` npm dist-tag and consume in toga25-supply (see the "Blox _sandbox-dev tag workflow" memory).
40
+ 3. **App wiring (dynamic):** in `useGenericTableViewModel` (or GenericTableLayout), when the level is tier-2/swap, attach the "See units" suffix to the FIRST `isVisible` field of `tableMeta.fields` (user's targeting rule — no per-field/client naming). GenericNestedTables already knows the level is a swap/nested tier (that's where `buildSwapTriggerColumns` lives).
41
+ 4. **Then REMOVE:** the `see-units` action column in `useInventoryPageViewModel.tsx` AND the `columnWidths={{ description: 420 }}` hack in `GenericTableLayout.tsx` — both become unnecessary.
42
+ - **Optional real fix for the toga-blox clip bug:** `.previewText` in `ExpandableCell`'s `Cellstyles.module.css` has no `overflow:hidden`/`text-overflow:ellipsis`, so the fixed-width `.toggle` clips in any too-narrow column. Adding those 2 CSS props would make "Show more" fit at any width and remove the need to hard-size the description column at all.
43
+
44
+ ## Current file state
45
+ | File | Status | Notes |
46
+ |------|--------|-------|
47
+ | toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx | Modified | Added `buildSwapTriggerColumns`, `getModalUuid` to TableLevel; renderLevel swap-trigger logic; urlParamKey doc comment. DONE/keep. |
48
+ | toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx | Modified | `getModalUuid` prop + wraps handleRowClick (keep). `columnWidths={{ description: 420 }}` STOPGAP — REMOVE when in-cell chevron lands. |
49
+ | toga25-supply/src/layout/GenericNestedTables/useGenericTableViewModel.tsx | Modified | `markExpandableCells(meta, ["description"])` + early-return fix (keep). Will also host the "first-column suffix" wiring next session. |
50
+ | toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx | Modified | `makeSeeUnitsColumns` (chevronsRight + BaseToolTip "See units", position:1, top-aligned), `getModalUuid`, `renderModal`. The action-column approach is TO BE REPLACED by the in-cell suffix; makeSeeUnitsColumns render can be reused as the suffix component. |
51
+ | toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json | Modified | swap urlParamKeys prefixed `swap-`; button titles aligned to NYCHH; `modalKey:"itemRecord"` on items swap level. DONE. |
52
+ | toga25-supply/src/pages/Inventory/viewModel/FIELDS/NYCHH/inventoryGroupings.json | Modified | swap urlParamKeys prefixed `swap-`; `modalKey:"itemRecord"` on items swap level. DONE. |
53
+ | dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql | Created | Hidden Items.uuid on items-for-purchase-orders table view. RUN on Client_Nychh locally; still needs running on other envs/clients at rollout. |
54
+
55
+ ## Decisions made
56
+ - **Swap trigger as a dedicated control, not whole-row.** Rationale: frees the row click for the record modal; a nested row now supports both "drill to child" and "open this record." Rejected: whole-row swap (original behavior — no room for modal).
57
+ - **`swap-` PREFIX on urlParamKey (not suffix).** Rationale: the modal/active-row param is literally the slug, and a `${slug}_*` key is parsed as a column filter — a prefix avoids both collisions. Rejected: `_swap` suffix (would be misread as a filter).
58
+ - **Resolve modal uuid via `getModalUuid` + hidden `Items.uuid` projection.** Rationale: reuses the existing item-record modal unchanged; keeps the line-vs-catalog distinction explicit. Rejected: a new modal keyed by SalesOrderItem; changing the generic handleRowClick globally.
59
+ - **Target the chevron by "tier-2 first column" (user's call this session).** Rationale: more dynamic than naming `description` — survives clients whose first column differs. Still needs the toga-blox cell-suffix slot to render inside the cell.
60
+
61
+ ## Blockers
62
+ - The final requested behavior (chevron INSIDE the first cell's content) is blocked on a small **toga-blox** change — the app cannot render into a toga-blox-built cell. Requires editing `~/Agilant/toga-blox-npm` and republishing under the `_sandbox-dev` dist-tag, then consuming in toga25-supply.
63
+
64
+ ## Exact next step
65
+ > In `~/Agilant/toga-blox-npm`, add an optional `cellSuffix` render slot to `resolveCellType` (render it as a sibling after `content` inside the existing `cellWrapperVariants` flex div) and thread `cellSuffixByField?: Record<slug,(row)=>ReactNode>` through `buildTanstackColumns` → `useTableSetup` → PrimaryTable props. Then publish `_sandbox-dev`, wire it in `useGenericTableViewModel` to attach the "See units" suffix to the first visible field of tier-2/swap levels, and delete both the `see-units` action column (useInventoryPageViewModel.tsx) and the `columnWidths={{ description: 420 }}` stopgap (GenericTableLayout.tsx).
66
+
67
+ ---
68
+ _Saved by /session-save on 2026-07-21_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.404",
3
+ "version": "1.0.406",
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",