toga-ai 1.0.748 → 1.0.750
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,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: talos-surface-layer
|
|
4
|
+
title: Talos moved onto the surface layer; chat history, titles, launcher dim
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga25-supply, toga-blox, dbchanges2]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: nychh
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-09-02
|
|
11
|
+
updated: 2026-09-02
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: talos-surface-layer
|
|
15
|
+
**Date:** 2026-09-02
|
|
16
|
+
**Project/Repo:** toga25-supply + toga-blox + dbchanges2 (2.0)
|
|
17
|
+
**Task:** Move the Talos AI assistant (feature gate + suggestion chips) off hardcoded frontend config onto the backend surface layer — off for all clients in Core, granted only to NYCHH — and finish the chat history / auto-title / launcher work started earlier in the same thread.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
|
|
23
|
+
- **Loading bubble restored in host-render mode.** `toga-blox src/components/Talos/TalosPanel.tsx` now renders the synthetic thinking bubble in BOTH modes, because it is chrome derived from `isStreaming`/`progress`, not a message. Evidence: 2 new regression tests; blox Talos suite 31 passing.
|
|
24
|
+
- **Chat history drawer + new chat, data-driven.** New controlled props `threads`, `activeThreadId`, `onSelectThread`, `onNewChat`, `threadsStatus`. `history` re-enables only when `threads` AND `onSelectThread` are both given; `newChat` only when `onNewChat` is. The panel sorts rows newest-first. Evidence: tests incl. ordering and "host content replaces the list".
|
|
25
|
+
- **Opening an existing thread needs no transcript parser.** Verified in `node_modules/@langchain/langgraph-sdk/dist/ui/orchestrator.js` — `setThreadId` calls `#fetchHistoryForThread` and clears the stream when `fetchStateHistory: true`. Selecting a row is just "set threadId"; the existing LangGraph→blox mapper handles the rest.
|
|
26
|
+
- **Auto-title polling.** `toga25-supply src/hooks/useTalosThreads.ts` `titlePollInterval` — 4s `refetchInterval` while any recent thread is unnamed, `false` otherwise, with a 2-minute age window. Evidence: 7 tests incl. server-clock-ahead and the never-named case.
|
|
27
|
+
- **"Couldn't load your chats." over a full list fixed.** `threadsStatusFrom` returns "error" only when there is no data; blox gates all three drawer notes on `histRows.length === 0`. Evidence: regression test in each repo.
|
|
28
|
+
- **Talos on the surface layer, RUN AND VERIFIED ON LOCAL.** `dbchanges2 Core/2026-09-02a - TalosAssistantSurfaceSeed.sql` → 2 elements, both `isVisible = 0`, suggestions row `itemCount = 4`. `dbchanges2 Client_Nychh/2026-09-02a - TalosAssistantEnable.sql` Section A → 3 rows: two `IS_VISIBLE='1'` + one `CONFIG` with `itemCount = 3` and `roleRestated = 'suggestion'`. Three NYCHH chips render in the browser. Developer confirmed: "that workds".
|
|
29
|
+
- **Fail-closed FE gate.** `src/hooks/useTalosSurface.ts` — missing bundle, failed fetch, absent marker, or still-loading all resolve to disabled. `TalosStreamProvider` gained an `enabled` prop so a tenant without Talos never calls `/assistants`. 14 tests.
|
|
30
|
+
- **Launcher dims under a modal.** `toga-blox TableRecordModal.tsx` sets `data-blox-modal-open` on `<html>` (a counter); `Talos.module.css` dims `.launcher` and stops the orbit ring under `:root[data-blox-modal-open]`.
|
|
31
|
+
- **Surface bundle no longer cached to disk.** `src/App.tsx` `shouldDehydrateQuery` excludes the `talos`, `surface-meta` and `surface-meta-group` namespaces.
|
|
32
|
+
- **End state:** toga25-supply 87 tests / 12 files passing; `npx tsc --noEmit -p tsconfig.app.json` clean apart from one pre-existing error; toga-blox 31 Talos + TableRecordModal tests passing; blox `dist/` rebuilt.
|
|
33
|
+
|
|
34
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
35
|
+
|
|
36
|
+
- **A host-render branch that bypasses the panel's own message list.** The first `renderMessages` implementation replaced `msgs` entirely, which took the synthetic thinking bubble with it. Symptom: submit a question and get **zero** feedback until the answer appears — no loading UI, no streaming. The bubble is chrome, not conversation; never let a host slot remove it.
|
|
37
|
+
- **Trusting supply's typecheck to prove a blox change reached the browser.** `npx tsc --noEmit -p tsconfig.app.json` resolves blox **types from source** through the `node_modules/@agilant/toga-blox` symlink, but Vite loads the package at **runtime from `dist/`**. Typecheck was clean while the browser ran stale blox code. Always `npm run build` in blox + `rm -rf node_modules/.vite` in supply.
|
|
38
|
+
- **The earlier note that blox `npm run build` fails on a missing `tsconfig.app.json` is WRONG.** It builds fine (`tsc && copyfiles … && fix-esm-imports && fix-asset-imports`). Do not route around it.
|
|
39
|
+
- **`status: isError ? "error" : …` for a React Query list.** React Query sets `isError` when a **background** refetch fails while KEEPING the data it already had. With the 4s title poll running, one failed poll printed "Couldn't load your chats." above a full, usable list. Report error only when there is no data.
|
|
40
|
+
- **A drawer error message with no empty-list guard.** It rendered *alongside* the rows, producing a screen that says loading failed while showing the loaded data.
|
|
41
|
+
- **Building the title polling before checking that ANY thread had a title.** Every NYCHH thread reads "New conversation", including ones days old and far outside any poll window — so this was never a client timing bug. Naming is a per-assistant setting (`thread_name_generation_enabled`, default `true`, plus `thread_name_model`) on `GET`/`PATCH /api/assistants/{assistant_id}/config`. The polling is correct and will work the moment the server writes a name, but it could not fix this.
|
|
42
|
+
- **A cross-schema `JOIN Core.SurfaceElements` in a `Client_*` file.** Works on local/beta, **fails in production** — Core and Client_* are on separate clusters. Error there: `Table 'Core.Surfaces' doesn't exist`. Prod needs the read-ids-on-Core → insert-literals-on-client two-step.
|
|
43
|
+
- **Schema-qualifying a Core-only migration (`Core.Messages`, `Core.Surfaces`).** api2 can rename the Core DB via `[database] core_dbname`, so qualifying breaks exactly the environment that renamed it. Core-only files must use UNQUALIFIED names and be run while connected to Core.
|
|
44
|
+
- **Running an unqualified `SurfaceElements` query while connected to `Client_Nychh`.** Error: `Table 'Client_Nychh.SurfaceElements' doesn't exist`. `SurfaceElements`/`Surfaces`/`Messages` are **Core-only**; a cross-schema verify must qualify every Core table.
|
|
45
|
+
- **Assuming the launcher-not-dimmed bug was z-index.** It was not. `.launcher` has no z-index, blox `.header` has no z-index or position, supply's `.headerActions` has neither, and `.backdrop` is `position: fixed; z-index: 40` — so the launcher was already painted under the backdrop and its clicks already swallowed. The real cause: `.backdrop-right`/`.backdrop-left` is only `rgba(0, 0, 0, 0.2)`, and a 20% black wash over a **white** button still reads as active. Do not go looking for a z-index.
|
|
46
|
+
- **One surface element per suggestion chip.** Replaced before it shipped past local. A tenant could only switch on questions **Core had already seeded**, so changing a tenant's wording needed a new Core migration for every phrasing.
|
|
47
|
+
- **A bare JSON array as a `CONFIG` override value.** A `CONFIG` override **replaces** `config` wholesale — `_underscore Model/Core/Surface.php:839` assigns, it does not `array_merge`. A bare array passes the `is_array()` check and wipes `config.role`, so the frontend stops recognising the element and the chips **silently disappear with no error anywhere**. The override must restate `"role":"suggestion"`.
|
|
48
|
+
- **Putting override JSON in `SurfaceOverrides.value`.** It is `varchar(255)`; any real chip set overflows it. Use `c_longValue` — the resolver falls back to it when `value` is NULL/'' (`Surface.php:907`).
|
|
49
|
+
|
|
50
|
+
## Not tried yet (candidates for next session)
|
|
51
|
+
|
|
52
|
+
- **Run the SQL on beta and prod.** Local only so far. Prod = Section B (read ids on the Core cluster) then Section C (literal-id insert on the client cluster). **Never Section A in prod.**
|
|
53
|
+
- **Wire Stop to `cancelTalosRun`** instead of `stream.stop()`. This is a **real defect**: `stream.stop()` only drops the client transport while the run keeps executing and billing. Needs `run_id` from the `metadata` SSE event (handoff §8.5).
|
|
54
|
+
- **`fromTalosTranscript()` + terminal-completion reconciliation** (handoff §8.11 / §8.8). Message ids in `src/providers/TalosRuntimeProvider.tsx` are `talos-${index}` placeholders; feedback needs a durable server `message_id` + `run_id`.
|
|
55
|
+
- **Message actions** (copy, thumbs up/down) via `ActionBarPrimitive`, and the thumbs-down dialog (handoff §9). Blocked on the durable ids above; copy alone is doable now.
|
|
56
|
+
- **Rename / delete thread UI.** `renameTalosThread` / `deleteTalosThread` already exist in `src/api/talos.ts` and are unused. Both endpoints are real (`PATCH`/`DELETE /threads/{thread_id}`). Decide the `If-Match` question first: the handoff doc shows it quoted, the live spec types it as a plain optional string, and our client sends it bare — recommendation was to **drop the header** rather than guess.
|
|
57
|
+
- **Make supply's header `inert` while a modal is open.** The launcher is still keyboard-reachable behind a modal; `pointer-events: none` stops the mouse only. Affects all header controls, so it is a deliberate supply-wide change.
|
|
58
|
+
- **Rule on reserved-literal ids vs `AUTO_INCREMENT`** for Core surface seeds. `knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md` currently documents BOTH branches. Note the shipped `Core/2026-08-12a` navigation seed already uses `AUTO_INCREMENT`, so the "never AUTO_INCREMENT" rule was already contradicted by merged code.
|
|
59
|
+
- **Per-role Talos gating / per-role chip sets.** Current grants are client-wide (`roleId` NULL) — every NYCHH user incl. devteam gets Talos. Two role-scoped `CONFIG` rows would resolve by `ORDER BY (roleId IS NULL) DESC, (personaId IS NULL) DESC, id ASC`, so the **higher id wins arbitrarily** for a multi-role user.
|
|
60
|
+
|
|
61
|
+
## Current file state
|
|
62
|
+
|
|
63
|
+
| File | Status | Notes |
|
|
64
|
+
|------|--------|-------|
|
|
65
|
+
| `dbchanges2 Core/2026-09-02a - TalosAssistantSurfaceSeed.sql` | new, uncommitted, RUN ON LOCAL | Surface `talos-assistant` (DRAWER, not record-bound), 2 elements both `isVisible=0`; suggestions element holds Core's default 4 items. Unqualified table names. Teardown in footer. |
|
|
66
|
+
| `dbchanges2 Client_Nychh/2026-09-02a - TalosAssistantEnable.sql` | new, uncommitted, RUN ON LOCAL (Section A only) | 3 sections: A single-cluster, B read ids on Core, C literal insert on client. Writes 2 `IS_VISIBLE` grants + 1 `CONFIG` with NYCHH's 3 questions. |
|
|
67
|
+
| `toga25-supply db-migrations/PLAYBOOK.md` | modified | 2026-09-02 change-log entry incl. prod cross-cluster procedure and the `SurfaceOverrides` NULL-unique-key gotcha. |
|
|
68
|
+
| `toga25-supply src/hooks/useTalosSurface.ts` (+ `.test.ts`) | new | Fail-closed gate + tolerant `readItems` parser. 14 tests. |
|
|
69
|
+
| `toga25-supply src/hooks/useTalosThreads.ts` (+ `.test.ts`) | new | Thread list, `titlePollInterval`, `threadsStatusFrom`, `toThreadSummary`. 12 tests. |
|
|
70
|
+
| `toga25-supply src/providers/TalosStreamProvider.tsx` | modified | `enabled` prop; threads/selectThread/activeThreadId in context; refresh on threadId assignment + run-end. |
|
|
71
|
+
| `toga25-supply src/providers/TalosRuntimeProvider.tsx` | new (earlier in thread) | assistant-ui `ExternalStoreRuntime` bridge. Message ids are index placeholders. |
|
|
72
|
+
| `toga25-supply src/components/TalosThread/` | new (earlier in thread) | assistant-ui thread + safe markdown components. |
|
|
73
|
+
| `toga25-supply src/hooks/useTalosSession.ts` | modified | Takes `enabled`; returns `disabled` when gated off. |
|
|
74
|
+
| `toga25-supply src/layout/AppLayout/AppLayout.tsx` | modified | Gates launcher + panel on `useTalosSurface().isEnabled`; passes surface suggestions, threads, history handlers. |
|
|
75
|
+
| `toga25-supply src/App.tsx` | modified | `shouldDehydrateQuery` excludes `talos`, `surface-meta`, `surface-meta-group`. |
|
|
76
|
+
| `toga-blox src/components/Talos/TalosPanel.tsx` | modified | `renderMessages`, thread-list props, loading bubble in both modes, `NO_SUGGESTIONS` default (DEFAULT_SUGGESTIONS deleted). |
|
|
77
|
+
| `toga-blox src/components/Talos/types.ts` | modified | `TalosThreadSummary`, thread props, `renderMessages`. |
|
|
78
|
+
| `toga-blox src/components/Talos/Talos.module.css` | modified | `.historyNote`; launcher dim under `:root[data-blox-modal-open]`. |
|
|
79
|
+
| `toga-blox src/components/Talos/README.md` | modified | Suggestions are host-supplied; chat-history section. |
|
|
80
|
+
| `toga-blox src/components/TableRecordModal/TableRecordModal.tsx` | modified | `data-blox-modal-open` counter on `<html>`. |
|
|
81
|
+
| `toga-blox src/components/TableRecordModal/tableRecordModal.module.css` | modified (earlier in thread) | `--talos-dock-width` awareness. |
|
|
82
|
+
| `toga-blox dist/` | rebuilt | Required for the browser; gitignored. |
|
|
83
|
+
|
|
84
|
+
**Nothing is committed in any of the three repos.**
|
|
85
|
+
|
|
86
|
+
## Decisions made
|
|
87
|
+
|
|
88
|
+
- **Talos is gated by the surface layer, OFF for every client in Core.** Rationale: Talos answers questions from a tenant's live Supply data through an external AI service; a tenant must not inherit that from a migration. Rejected: a build-time env flag (not per-tenant) and defaulting ON (a data-exposure decision made by a file instead of a person).
|
|
89
|
+
- **The feature switch is a marker element, not `Surfaces.isVisible`.** The override cascade is keyed by `surfaceElementId` and is never applied to a Surfaces row's own `isVisible`, so overriding the surface row silently does nothing. Same trick as the `config.role='sectionVisibility'` markers.
|
|
90
|
+
- **Chips are ONE element with a `config.items` array of `{label, prompt, icon}`.** Rationale: per-chip elements let a tenant only enable questions Core had already seeded. Rejected: one element per chip (needs a Core migration per phrasing) and an array of bare strings (collapses label/prompt/icon and loses the icons). Trades accepted: labels are not `Messages` keys so `MessageTranslations` cannot translate them; and chip SETS are per-scope rather than per-chip.
|
|
91
|
+
- **The chip payload lives in `config`, not an `Actions` row with `type='RUN_TALOS'`.** `RUN_TALOS` is in the enum but **no `Actions` row has ever been seeded in dbchanges2** and there is no FE action-registry entry for it. `config` is emitted verbatim into the bundle. Same trade `Core/2026-08-12a` made choosing `config.path` over `Actions`/`NAVIGATE`.
|
|
92
|
+
- **`AUTO_INCREMENT` + `NOT EXISTS` guards on natural keys, not a reserved literal id block.** Reserving requires verifying `MAX(id)` against every environment first; these files are purely additive and id-agnostic. Matches the `navigation` seed. Rejected: reserved literals (unverifiable without DB access, and the ids are read off Core at run time in Section B anyway).
|
|
93
|
+
- **The per-client file keeps `Client_Nychh.` qualification while the Core file drops `Core.`.** Run against the wrong cluster the qualified one fails loudly on a missing schema instead of silently granting the feature to another tenant; the Core schema name is not guaranteed.
|
|
94
|
+
- **The `NOT EXISTS` guard is load-bearing, not decoration.** `SurfaceOverrides`' unique key is `(surfaceElementId, surfaceId, personaId, roleId, languageId, attribute)` and MySQL treats NULLs in a unique key as **distinct** — client-wide grants have persona/role/language all NULL, so the index accepts duplicates.
|
|
95
|
+
- **Permission/feature surfaces are not persisted to localStorage.** `surface-meta` is fetched with `staleTime: Infinity`, so a persisted copy is never refetched and a grant — or a **revoke** — would not reach a browser for up to the persister's 24h default `maxAge`.
|
|
96
|
+
- **The modal-open marker is a root-element attribute and a counter.** An attribute because the launcher renders outside the modal's portal so no React state reaches it (same boundary-crossing trick as `--talos-dock-width`); a counter because modals stack and the marker must survive the inner one closing. Rejected: threading a prop from every modal layout.
|
|
97
|
+
- **`config.items` parsing is tolerant, not strict.** `config` is a hand-edited JSON column that differs per tenant, so one bad item costs one chip rather than every chip or a render crash.
|
|
98
|
+
|
|
99
|
+
## Blockers
|
|
100
|
+
|
|
101
|
+
- **Thread auto-titles are broken server-side.** Every NYCHH thread reads "New conversation". Naming is per-assistant: `thread_name_generation_enabled` (default `true`) + `thread_name_model` on `GET`/`PATCH /api/assistants/{assistant_id}/config`. Either it is `false` on the `TOGa Supply` assistant or the Bedrock naming model is unreachable in that deployment. **A consumer app cannot fix this.** Open with `akhokhani`; NYCHH `assistant_id` `c7981164-1fe9-5878-b115-85c38dadad43`.
|
|
102
|
+
- **Nothing is committed in toga25-supply, toga-blox or dbchanges2.** blox `dist/` is gitignored, so no other consumer sees any of this until blox publishes.
|
|
103
|
+
- **Beta and prod SQL not run** — the feature is invisible in those environments (the fail-closed gate working, not a regression).
|
|
104
|
+
|
|
105
|
+
## Exact next step
|
|
106
|
+
|
|
107
|
+
> Wire Stop to `cancelTalosRun` in `toga25-supply src/providers/TalosStreamProvider.tsx` — replace `stream.stop()` in the `stop` callback. It is a real defect: `stream.stop()` drops only the client transport while the run keeps executing and billing on Aegra. You need the `run_id` from the `metadata` SSE event (handoff §8.5), so capture it in the `useStream` `onCustomEvent`/metadata handler first, then call the existing `cancelTalosRun(token, { threadId, runId })` from `src/api/talos.ts`.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
_Saved by /session-save on 2026-09-02_
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: transfer-order-create-modal
|
|
4
|
+
title: Create Transfer Order modal — single-POST create + design-matched UI
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga25-supply, _underscore, dbchanges2, api2]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: nychh
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-09-02
|
|
11
|
+
updated: 2026-09-02
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: transfer-order-create-modal
|
|
15
|
+
**Date:** 2026-09-02
|
|
16
|
+
**Project/Repo:** toga25-supply (+ _underscore, dbchanges2, api2) — 2.0
|
|
17
|
+
**Task:** Build the Create Transfer Order modal for NYCHH end to end — one atomic POST, the three new `TransferOrders` columns behind it, and the picker/dropdown UI matched to the Claude Design mockup.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
|
|
23
|
+
- **Single POST create.** `useCreateTransferOrder.ts` now posts header + lines in one request to `/transfer-orders`, lines nested under `transferOrderItems`. Verified against the engine, not guessed: `api2/Component/Api/V2/V2.php:3248` converts the inherent child's `route` kebab→camel, and `:5241` uses the SAME lookup on the CREATE path (`$childrenToCreate[$key] = $val`). Jeff's nesting row exists — `InherentRecordChildren` id 152, parent record 312 → child 313, route `transfer-order-items` (confirmed by query).
|
|
24
|
+
- **Three new columns.** `_underscore/Model/Client/TransferOrder.php` has `contactId` (FK Contacts), `createdByUserId` (FK Users), `description` (varchar 255). Migrations written and guarded with `INFORMATION_SCHEMA` checks.
|
|
25
|
+
- **Core ids verified against production before use.** `Records` MAX(id) = 358; RecordFields 2561–2563 free. Handoff for the team: next `Records.id` = 359, next `RecordFields.id` = 2564.
|
|
26
|
+
- **TransferOrderNotes tables + metadata ran clean.** `Client/2026-09-01b` and `Core/2026-09-01c` are applied (Records 357/358, RecordFields 2550–2560). Both tables ship empty. NOTE: now unused — see Decisions.
|
|
27
|
+
- **Target Location picker.** `SelectTargetLocationModal.tsx` — flat list, 250 ms debounced search, breadcrumb path built from `parentLocation.uuid` with a cycle guard.
|
|
28
|
+
- **Contact scoping.** Contacts come from `Contacts` through the `Contacts_Locations` junction, scoped to the chosen location (`useLocationContacts.ts`).
|
|
29
|
+
- **One select control for the whole modal.** New app-local `PanelSelect.tsx` — 38px trigger, search box inside the panel, green check on the chosen row, optional pinned footer, optional clear (×). `ContactSelect.tsx` is now a thin mapping over it. The PO filter uses it at 200px with "All POs" as a row in the list. Matches the mockup's `window.FormSelect` / `FilterLockSelect`.
|
|
30
|
+
- **"Show more" on the item cell.** `TransferItemPickerTable.tsx`, rules copied from blox `PrimaryTableBodyCell`: offered only when text is really clipped (measured on hover), collapsed visibility is CSS `group-hover` not state, expanded wraps above a "Show less" toggle and grows the row (body cells align top), toggle `stopPropagation`s so it never checks the row. Cells cap at 300px so clipping can happen at all in an auto-layout table. Items table max height 355px.
|
|
31
|
+
- **Blank Item column fixed.** Label chain is `item.title || item.description || "(untitled item)"`, and `vendorItem.item.description` was added to the requested fields.
|
|
32
|
+
- **Verification each time:** `npx tsc --noEmit` exits 0; `npx eslint src/pages/TransferOrders/view/CreateTransferOrderModal` stays at the same 8 pre-existing errors (2 `set-state-in-effect`, 1 `react-hooks/refs`, 4 `no-explicit-any`, 1 `react-refresh/only-export-components`). Baseline was established with `git stash`, so "zero new errors" is measured, not assumed.
|
|
33
|
+
|
|
34
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
35
|
+
|
|
36
|
+
- **Three sequential POSTs to create a transfer.** Abandoned: it can leave a half-written transfer behind. The inherent-child single POST is one API transaction, which the front controller commits on success and rolls back on failure.
|
|
37
|
+
- **A scripted API endpoint for transfer create.** Not needed once the inherent child works. Developer decided no.
|
|
38
|
+
- **`useCallback` around the modal's `submit`.** React Compiler bailed with "Compilation Skipped: Existing memoization could not be preserved." Fixed by making `submit` a plain function. Do not wrap it again.
|
|
39
|
+
- **A `useEffect` that called `close()` when the select went disabled.** Tripped `react-hooks/set-state-in-effect`. Replaced with derived state: `const isPanelOpen = isOpen && !disabled;`.
|
|
40
|
+
- **Bare FK column names in the `fields` option.** I wrote `parentLocationId` and it returned nothing. This repo uses dotted paths — `parentLocation.uuid`, `vendorItem.item.title` — and FKs POST as nested `{ uuid }` objects.
|
|
41
|
+
- **A native `<select>` for the PO filter.** Looked like a different control next to the custom contact select. The mockup draws both with the same `FormSelect`.
|
|
42
|
+
- **Falling back to a part number for the item label.** Part Number already has its own column, so it duplicates a cell instead of naming the item.
|
|
43
|
+
- **`Locations.primaryContactId` as a default-contact FK.** Developer rejected it: `Contacts_Locations` is the right place.
|
|
44
|
+
- **A unique index on `Contacts_Locations (locationId, type)`.** Wrong — it is many-to-many, many contacts per location. Only `isPrimary` is viable.
|
|
45
|
+
- **My `_qty*` diagnosis "no ItemReceiptItems exist, it's data."** Wrong. The developer's query returned `216 | 80622 | 3270.00 | 27 | 3270.00` — receipts do exist. The seed-the-receipts plan built on that was also scrapped.
|
|
46
|
+
- **`AUTO_INCREMENT` / repo-derived ids for `Records` / `RecordFields`.** Never do this. They are the only two tables platform-wide with team-assigned ids; ask the developer, and the repo lags production.
|
|
47
|
+
- **Python heredoc edits that assume indentation depth.** Failed twice (`AssertionError: ('type', 0)`) — assumed 4 tabs where it was 3, and 10 where it was 11. Print `repr()` first or slice by index. The asserts fire before the write, so no file was corrupted.
|
|
48
|
+
|
|
49
|
+
## Not tried yet (candidates for next session)
|
|
50
|
+
|
|
51
|
+
- A live POST of the create flow. **Nothing in this modal has been sent to a real API yet.** Blocked on the two pending migrations + the `_underscore` deploy.
|
|
52
|
+
- Setting `transferOrderStageId` on create — unknown which stage row NYCHH seeds as the opening one.
|
|
53
|
+
- Minting `TransferOrders.number`. Deliberately not done client-side: two concurrent creates would race. Needs a server-side owner.
|
|
54
|
+
- Stamping `createdByUserId` from the JWT server-side instead of from the browser's `user` localStorage key.
|
|
55
|
+
- Dropping the now-unused `TransferOrderNotes` / `TransferOrderNoteTypes` tables and their Core metadata (Records 357/358, RecordFields 2550–2560).
|
|
56
|
+
- Adding the ACL grant for NYCHH on records 357/358 — currently missing (only needed if the notes tables stay).
|
|
57
|
+
- Adding the magnifier icon inside the item search box (the mockup has one on the left; ours has none).
|
|
58
|
+
- Populating `Items.title` for NYCHH — the real fix behind the description fallback.
|
|
59
|
+
|
|
60
|
+
## Current file state
|
|
61
|
+
|
|
62
|
+
| File | Status | Notes |
|
|
63
|
+
|------|--------|-------|
|
|
64
|
+
| `toga25-supply/.../CreateTransferOrderModal/hooks/useCreateTransferOrder.ts` | Modified | Single POST; exports `DESCRIPTION_MAX_LENGTH = 255`; omits `number` on purpose |
|
|
65
|
+
| `toga25-supply/.../hooks/usePurchaseOrderItemRows.ts` | Modified | `item.description` fallback; `VITE_TRANSFER_IGNORE_AVAILABILITY` escape hatch |
|
|
66
|
+
| `toga25-supply/.../hooks/useTargetLocationOptions.ts` | Modified | `parentLocation.uuid` dotted path; breadcrumb path + cycle guard |
|
|
67
|
+
| `toga25-supply/.../hooks/useLocationContacts.ts` | Modified | Contacts via `Contacts_Locations`, scoped to the location |
|
|
68
|
+
| `toga25-supply/.../hooks/useCreateLocationContact.ts` | Modified | Inline add-contact |
|
|
69
|
+
| `toga25-supply/.../view/SelectTargetLocationModal.tsx` | Created | Flat picker, debounced search, 720px card |
|
|
70
|
+
| `toga25-supply/.../view/PanelSelect.tsx` | Created | The one select control; `// TODO(blox): promote once stable` |
|
|
71
|
+
| `toga25-supply/.../view/ContactSelect.tsx` | Rewritten | Now a thin wrapper over `PanelSelect` |
|
|
72
|
+
| `toga25-supply/.../view/TransferItemPickerTable.tsx` | Modified | 355px max height, column lines, `ExpandableText` show-more, `align-top` cells |
|
|
73
|
+
| `toga25-supply/.../view/TransferNotesField.tsx` | Modified | Optional `maxLength`, wired to 255 |
|
|
74
|
+
| `toga25-supply/.../CreateTransferOrderModal.tsx` | Modified | `PanelSelect` for PO (200px, "All POs" row), plain `submit`, inline error |
|
|
75
|
+
| `_underscore/Model/Client/TransferOrder.php` | Modified | `contactId`, `createdByUserId`, `description` — **not deployed** |
|
|
76
|
+
| `_underscore/Model/Client/TransferOrderNote.php` | Created | Mirrors `PurchaseOrderNote` — currently unused |
|
|
77
|
+
| `_underscore/Model/Client/TransferOrderNoteType.php` | Created | Mirrors `PurchaseOrderNoteType` — currently unused |
|
|
78
|
+
| `dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql` | Applied | Ran |
|
|
79
|
+
| `dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql` | Applied | Ran — Records 357/358, RecordFields 2550–2560 |
|
|
80
|
+
| `dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql` | **Not run** | 7 `INFORMATION_SCHEMA`-guarded sections; run on every client DB |
|
|
81
|
+
| `dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql` | **Not run** | RecordFields 2561–2563 on record 312 + ACL roles 1 and 3 |
|
|
82
|
+
|
|
83
|
+
All application-repo changes sit uncommitted in the working tree on branch `features-talos-transferorders`. Nothing was committed or pushed.
|
|
84
|
+
|
|
85
|
+
## Decisions made
|
|
86
|
+
|
|
87
|
+
- **One POST, not a scripted endpoint.** The inherent child gives the same all-or-nothing write, so a custom endpoint buys nothing. Rejected: three sequential POSTs (can half-write), a scripted API method.
|
|
88
|
+
- **The modal's Notes box writes `TransferOrders.description`, not a note row.** A transfer carries one free-text description, not a thread of typed notes from different authors. Consequence: `TransferOrderNotes` / `TransferOrderNoteTypes` are now unused even though they are already migrated. Rejected: posting a `TransferOrderNotes` row per create.
|
|
89
|
+
- **`createdByUserId` is client-supplied** (read from the `user` localStorage key), matching the SalesOrders precedent. It is only as trustworthy as this client — still open for Jeff. Rejected for now: JWT-stamped server-side.
|
|
90
|
+
- **`number` is left NULL.** Minting max+1 from the browser races two concurrent creates. Rejected: client-side minting.
|
|
91
|
+
- **The contact list lives in `Contacts_Locations`,** not a `Locations` FK — the developer's call. Many contacts per location, so only `isPrimary` can mark a default.
|
|
92
|
+
- **`PanelSelect` is app-local, not blox.** Front-end standard §21: build it here, promote after a second consumer. blox's `AdvancedSelect` has `renderOption` but no footer slot, and a sentinel row inside `data` gets filtered out by the very search that makes you need it.
|
|
93
|
+
- **Grid geometry stays in the front end.** Cell max width, table max height and column lines are presentation, not surface metadata.
|
|
94
|
+
- **`VITE_TRANSFER_IGNORE_AVAILABILITY` is local-only.** It disables the guard that stops a transfer asking for more than can be dispatched. **Never set it in a deployed environment.**
|
|
95
|
+
|
|
96
|
+
## Blockers
|
|
97
|
+
|
|
98
|
+
- **`_qty*` returns 0 for every NYCHH PO line.** For PurchaseOrderItem `715ef031-003d-6742-871d-0d20de2fa40f` (PO 80622, line 21) the API gives `quantity: 3270` but `_qtyReceived` / `_qtyOnHand` / `_qtyAvailable` / `_qtyFulfilled` are all `0`, while SQL on `Client_Nychh` gives received 3270. A literal `0` (not absent) rules out field ACL and `calcDepth`. Physical shadowing columns also ruled out (the blank template has 7 columns on `PurchaseOrderItems`). The team KB already has the cause from 2026-09-01: the deployed `_Model_Nychh_PurchaseOrderItem` INNER JOINs `InventoryAdjustmentItems.itemFulfillmentItemId`, which is NULL on 100% of rows. Talk to Jeff as "this narrows the known blocker", not as a new bug. Worked around locally with `VITE_TRANSFER_IGNORE_AVAILABILITY=true`.
|
|
99
|
+
- **Two migrations + one deploy are pending,** so the create path cannot be tested live yet (see next step).
|
|
100
|
+
|
|
101
|
+
## Exact next step
|
|
102
|
+
|
|
103
|
+
> Run the pending rollout, in this order: (1) deploy `_underscore` so `TransferOrder.php` carries `contactId` / `createdByUserId` / `description`; (2) run `dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql` on every client DB; (3) run `dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql` on Core; (4) post `next Records.id = 359` / `next RecordFields.id = 2564` to the team; (5) create one test transfer in the modal with `VITE_TRANSFER_IGNORE_AVAILABILITY=true` and confirm the row has `contactId`, `createdByUserId`, `description`, and its `TransferOrderItems` rows written in the same request.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
_Saved by /session-save on 2026-09-02_
|
package/package.json
CHANGED