toga-ai 1.0.495 → 1.0.497
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/worker/INDEX.md +1 -0
- package/knowledge/1.0/apps/worker/workflows/diagnosing-frozen-cron-checkins.md +103 -0
- package/knowledge/2.0/apps/_underscore/features/email-send-pipeline.md +37 -1
- package/knowledge/2.0/apps/api2/INDEX.md +1 -1
- package/knowledge/2.0/apps/api2/features/request-logging.md +2 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/sessions/2026-08-03-toga25-loading-states-tcox.md +88 -0
- package/package.json +1 -1
|
@@ -10,5 +10,6 @@
|
|
|
10
10
|
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/netsuite/rest.php, library/app/systemmonitor/netsuiteintegration.php |
|
|
11
11
|
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php |
|
|
12
12
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
13
|
+
| [Diagnosing frozen 1.0 worker cron check-ins (Sentry "missed" flood)](workflows/diagnosing-frozen-cron-checkins.md) | When 1.0 worker cron timestamps freeze and Sentry project `worker1` fills with **`missed`** check-ins, the intuitive diagnosis — a wedged `App_Framework::isProc | worker/.ebextensions/cron.config, library/app/worker.php |
|
|
13
14
|
| [isFulfillable Multi-Client Backfill (all togasupply clients)](workflows/isfulfillable-multi-client-backfill.md) | One-time backfill that catches up `Items.isFulfillable` on **existing** items across **all 17 togasupply clients** (AIG, Broward Sheriff, Canon, Endeavor Health | worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php, library/app/api/toga2.php |
|
|
14
15
|
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Diagnosing frozen 1.0 worker cron check-ins (Sentry "missed" flood)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-03
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/.ebextensions/cron.config
|
|
13
|
+
- library/app/worker.php
|
|
14
|
+
related:
|
|
15
|
+
- ../architecture.md
|
|
16
|
+
- ../features/oneuptime-worker-uptime-monitoring.md
|
|
17
|
+
- ../../library/features/cron-execution-monitoring.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
When 1.0 worker cron timestamps freeze and Sentry project `worker1` fills with **`missed`**
|
|
23
|
+
check-ins, the intuitive diagnosis — a wedged `App_Framework::isProcessRunning()` lock — is
|
|
24
|
+
usually **wrong**. The common cause is **Elastic Beanstalk deployment churn replacing the EC2
|
|
25
|
+
instances underneath the crons**. This workflow is the ordered path that distinguishes the two
|
|
26
|
+
and the interpretation traps along the way.
|
|
27
|
+
|
|
28
|
+
## Where to look (name mapping)
|
|
29
|
+
|
|
30
|
+
Sentry project **`worker1`** is the 1.0 worker tier running in the Elastic Beanstalk
|
|
31
|
+
environment **`agilant-worker`** (application "Agilant", `us-west-2`, account
|
|
32
|
+
`502614707982`, reachable with the `default` SSO profile). It is a multi-instance autoscaled
|
|
33
|
+
fleet. **There is no EB environment literally named `worker1`** — the Sentry project name and
|
|
34
|
+
the EB environment name do not match, and looking for the wrong one costs real time.
|
|
35
|
+
|
|
36
|
+
## The failure mode
|
|
37
|
+
|
|
38
|
+
An EB deploy that aborts — events read like `Failed to deploy application`,
|
|
39
|
+
`Unsuccessful command execution on instance id(s) ...`,
|
|
40
|
+
`Cannot complete command execution ... no longer running` — causes EB to **replace
|
|
41
|
+
instances**. In-flight cron processes die with their host, and each Sentry monitor's
|
|
42
|
+
`lastCheckIn` freezes at whatever its now-terminated instance last reported. Nothing is
|
|
43
|
+
stuck; nothing needs clearing.
|
|
44
|
+
|
|
45
|
+
Two consequences worth knowing:
|
|
46
|
+
|
|
47
|
+
- **Check-ins resume on their own**, typically within ~1 minute of the deploy completing.
|
|
48
|
+
No manual intervention, no lock to clear, no instance to restart.
|
|
49
|
+
- EB may warn that an aborted deploy left instances on **mixed application versions**. That
|
|
50
|
+
does need a re-deploy of the intended version — the recovery of the crons does not imply
|
|
51
|
+
the fleet is on the version you meant to ship.
|
|
52
|
+
|
|
53
|
+
## Diagnostic path
|
|
54
|
+
|
|
55
|
+
1. **List the project's monitors and find the stall boundary.**
|
|
56
|
+
`GET https://sentry.io/api/0/organizations/<org>/monitors/?project=worker1` — read each
|
|
57
|
+
monitor's `environments[].lastCheckIn`. The cluster of timestamps that all stop at roughly
|
|
58
|
+
the same moment is the boundary.
|
|
59
|
+
2. **Pull per-monitor check-in history.**
|
|
60
|
+
`GET .../monitors/<slug>/checkins/` — a `missed` entry **with no `duration`** means the run
|
|
61
|
+
never started or never reported. A run that started and then hung looks different (it has a
|
|
62
|
+
start and no clean check-out). This is the first signal separating "process never launched"
|
|
63
|
+
from "process wedged".
|
|
64
|
+
3. **KEY DISCRIMINATOR — are non-monitored crons on the same host still throwing ordinary
|
|
65
|
+
exceptions in the same Sentry project's issue stream?** If other crons are still erroring
|
|
66
|
+
*after* the stall boundary, then PHP runs and Sentry connectivity both work and the host is
|
|
67
|
+
alive. That rules out "host down / Sentry unreachable" and points at instance replacement
|
|
68
|
+
or a per-cron blockage.
|
|
69
|
+
4. **Confirm against Elastic Beanstalk.**
|
|
70
|
+
```
|
|
71
|
+
aws elasticbeanstalk describe-events --environment-name agilant-worker --start-time <ISO>
|
|
72
|
+
aws elasticbeanstalk describe-environment-health --environment-name agilant-worker --attribute-names All
|
|
73
|
+
```
|
|
74
|
+
Correlate deploy / abort / instance-add / instance-remove timestamps against the stall
|
|
75
|
+
boundary. A deploy window that brackets the boundary is the answer.
|
|
76
|
+
|
|
77
|
+
## Interpretation caveats
|
|
78
|
+
|
|
79
|
+
- **Timezones.** Sentry returns **UTC**; the developer reporting the symptom is usually
|
|
80
|
+
quoting local **CDT (UTC-5)**. Convert before concluding the windows "don't line up" — they
|
|
81
|
+
usually do.
|
|
82
|
+
- **Establish each monitor's baseline before calling a stale timestamp new.** The `worker1`
|
|
83
|
+
monitor list chronically contains monitors that have been silently dead for weeks or months
|
|
84
|
+
for reasons unrelated to any current incident. During an incident these are noise and must
|
|
85
|
+
not be read as "failed to recover." (Deliberately not listing which ones — that goes stale.
|
|
86
|
+
Check each monitor's own history.)
|
|
87
|
+
- **Separate high-frequency from low-frequency monitors.** A 5-minute monitor should recover
|
|
88
|
+
within minutes; an hourly or daily one will not move until its next scheduled slot, and its
|
|
89
|
+
silence proves nothing.
|
|
90
|
+
- **Alerting-hygiene gap.** Chronically dead cron monitors mean nobody is being alerted on
|
|
91
|
+
those jobs at all. Worth a separate follow-up whenever you notice one.
|
|
92
|
+
|
|
93
|
+
## After recovery
|
|
94
|
+
|
|
95
|
+
A multi-minute cron outage on this tier means missed windows for order processing, email
|
|
96
|
+
sending, and inbound order imports. These are catch-up-on-next-run designs and generally
|
|
97
|
+
self-drain — but **eyeball queue depth / backlog after recovery rather than assuming clean**.
|
|
98
|
+
|
|
99
|
+
## Change history
|
|
100
|
+
|
|
101
|
+
- 2026-08-03 — Documented from a production diagnostic session: frozen cron timestamps +
|
|
102
|
+
`missed` check-in flood on `worker1` traced to EB deployment churn on `agilant-worker`, not
|
|
103
|
+
a wedged `isProcessRunning()` lock. (dfranks)
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-07-29
|
|
10
|
-
owners: ["bala", tcox]
|
|
10
|
+
owners: ["bala", "tcox", "dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Email.php
|
|
13
13
|
- worker2/Worker/Infrastructure/Email/Send.php
|
|
@@ -71,11 +71,47 @@ SMTP** using PHPMailer, then flips each row to `SENT` or `FAILED`. So a 2.0 emai
|
|
|
71
71
|
`DevTeam@goagilant.com` is a known-good verified sender identity in worker2 config. See
|
|
72
72
|
[ai-bdr web-funnel-app.md](../../ai-bdr/features/web-funnel-app.md).
|
|
73
73
|
|
|
74
|
+
- **⚠ READ-AFTER-WRITE ON THE READER REPLICA LOSES `send()`'S OWN LOG ROW (deterministic).**
|
|
75
|
+
`_Email::send()` INSERTs the `PENDING` `Logs_[Client].Email` row, then reads `$emailLog->id`
|
|
76
|
+
back. On a `_Model`, `save()` sets the PK in memory from `getInsertId()` but does **not** clear
|
|
77
|
+
`_model_needsToBeInitialized`, so the next `->id` access triggers `__get` → `initialize()`,
|
|
78
|
+
which issues a fresh `SELECT ... WHERE id=<new id>`. `DB_CLIENT_LOGS` is registered with a
|
|
79
|
+
**separate `readHost`**, so that SELECT is routed to the **reader replica** — but the INSERT is
|
|
80
|
+
still inside an **uncommitted** lazy transaction, so it has produced no binlog event and the
|
|
81
|
+
replica **can never** see it during this request. The read returns 0 rows and `initialize()`
|
|
82
|
+
throws `"exactly 1 row expected, 0"`. This is **100% deterministic, not a lag race** — an
|
|
83
|
+
uncommitted write cannot replicate. A connection *can* see its own uncommitted writes
|
|
84
|
+
(read-your-own-writes), so forcing the read onto the writer is the fix.
|
|
85
|
+
- **Why only some emails hit it:** approval/order-placed emails send from inside the **api2
|
|
86
|
+
CRUD POST path** (and worker2), which call `_Database::setIsReadHostEnabled(false)`
|
|
87
|
+
(`V2.php` create loop; worker2 `Controller/Index.php`) — their identical read-back hits the
|
|
88
|
+
**writer** and succeeds. The scripted-API path (`/email-templates/sendEmail`) never suppresses
|
|
89
|
+
the read host, so it throws every time. This was the root cause of the Compass In-Transit
|
|
90
|
+
email outage — see
|
|
91
|
+
[`compass-partial-in-transit-delivered-emails.md`](../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md).
|
|
92
|
+
- **Fix direction (smallest blast radius):** wrap the log-row `save()` + id read (+ attachment
|
|
93
|
+
loop) in `_Email::send()` with `_Database::setIsReadHostEnabled(false)`, restoring the prior
|
|
94
|
+
value in a `finally` (get/set at `Database.php`). Fixes all scripted-API emails and hardens the
|
|
95
|
+
interceptor path.
|
|
96
|
+
- **Rejected alternatives:** (a) `commit` right after `save()` before the read — recreates the
|
|
97
|
+
failure as an *intermittent* replication-lag race AND breaks the request's one-transaction
|
|
98
|
+
boundary (orphans the row on a later throw, prematurely commits everything else buffered on the
|
|
99
|
+
connection, strands attachment/API-log writes on a closed lazy transaction). (b) Clear
|
|
100
|
+
`_model_needsToBeInitialized` after INSERT in `_Model::save()` so `->id` reads from memory
|
|
101
|
+
(zero queries) — conceptually the *correct* fix but changes every model in the ORM, too broad
|
|
102
|
+
for a hotfix.
|
|
103
|
+
|
|
74
104
|
## Change history
|
|
75
105
|
- 2026-07-29 — Noted that the SES SMTP credentials/settings now have a **non-PHP consumer**: the
|
|
76
106
|
BDR funnel sends via nodemailer against the same SES host (us-west-2, port 587, TLS) using
|
|
77
107
|
`TOGA_SMTP_*` env vars, so a rotation must be coordinated with BDR's `.env.local` + Amplify
|
|
78
108
|
vars or BDR's summary email goes dark. (tcox)
|
|
109
|
+
- 2026-07-29 — Documented the **deterministic read-after-write failure** in `_Email::send()`:
|
|
110
|
+
reading `$emailLog->id` after INSERT triggers `_Model::__get`→`initialize()`→ a reader-replica
|
|
111
|
+
SELECT for an uncommitted row → `"exactly 1 row expected, 0"` throw. Only the scripted-API send
|
|
112
|
+
path fails (CRUD/worker paths suppress the read host via `setIsReadHostEnabled(false)`); this was
|
|
113
|
+
the root cause of the Compass In-Transit outage. Fix direction = suppress the read host around the
|
|
114
|
+
log-row save+read in `send()`. (dfranks)
|
|
79
115
|
- 2026-07-28 — Created: documented that `_Email::send()` **queues** (writes a `PENDING`
|
|
80
116
|
`Logs_[Client].Email` row + attachment BLOBs) rather than transmitting, and that the worker2
|
|
81
117
|
`Infrastructure/Email/Send` cron (`Core.CronJobs` `* * * * *`) does the actual SES SMTP send
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
| [Nested FK object embedding is gated by the CHILD record's own ACL](features/nested-fk-acl-embedding.md) | When the V2 JSON engine serializes a foreign-key field into a **nested object** (in `getFullModelData()`, ~V2.php L6016-6060), it re-checks the **child** record | api2/Component/Api/V2/V2.php, dbchanges2/Client_Compass/2026-07-23b - PurchaseOrdersRecordReadAcl.sql |
|
|
12
12
|
| [Nested-relationship writes & child matching (link vs. create)](features/nested-relationship-writes.md) | When a 2.0 API write payload (`POST`/`PUT`) contains a **nested related object** (e.g. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/ContactEmailAddress.php |
|
|
13
13
|
| [Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)](features/record-scripts.md) | In api2 you almost never write a controller. | api2/Component/Api/V2/V2.php, _underscore/Model/Team/Sprint.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Query.php |
|
|
14
|
-
| [V2 Request Logging & Where Requests Land (client vs core log DB)](features/request-logging.md) | The V2 engine logs **every inbound request** — success *and* failure, with response code and payload — and routes each log entry to the **client** log or the ** | api2/Component/Api/V2/V2.php, _underscore/Model/Client/Logs/Api.php, _underscore/Model/Core/Logs/Api.php |
|
|
14
|
+
| [V2 Request Logging & Where Requests Land (client vs core log DB)](features/request-logging.md) | The V2 engine logs **every inbound request** — success *and* failure, with response code and payload — and routes each log entry to the **client** log or the ** | api2/Component/Api/V2/V2.php, api2/Controller/Index.php, _underscore/Model/Client/Logs/Api.php, _underscore/Model/Core/Logs/Api.php |
|
|
15
15
|
| [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | The V2 engine can run a Record Script (scripted API) for a **POST** request, and a scripted API can receive its arguments from the **JSON request body** instead | api2/Component/Api/V2/V2.php |
|
|
16
16
|
| [TOGa IQ Sprint Dashboard API (Record Scripts)](features/sprint-dashboard-api.md) | The internal **TOGa IQ sprint dashboard** is served in production by **six api2 Record Scripts** on `_Model_Team_Sprint` (`_underscore/Model/Team/Sprint.php`), | _underscore/Model/Team/Sprint.php, api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-07-24a - SprintDashboardRecordScripts.sql, dbchanges2/Client_True/2026-07-24b - SprintDashboardScriptAcl.sql |
|
|
17
17
|
| [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | An opt-in V2 engine request option, `surface=<slug>`, that attaches per-record UI action state (`isVisible`/`isEnabled`) to a GET response **under `meta.surface | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
@@ -7,9 +7,10 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-07-29
|
|
10
|
-
owners: ["mhammontree"]
|
|
10
|
+
owners: ["mhammontree", "dfranks"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
|
+
- api2/Controller/Index.php
|
|
13
14
|
- _underscore/Model/Client/Logs/Api.php
|
|
14
15
|
- _underscore/Model/Core/Logs/Api.php
|
|
15
16
|
related:
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 14 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 18 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 10 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: toga25-loading-states
|
|
4
|
+
title: Design-mockup loading states for toga25-supply tables and modals
|
|
5
|
+
author: tcox
|
|
6
|
+
repos: [toga25-supply, toga-blox-npm]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-08-03
|
|
11
|
+
updated: 2026-08-03
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: toga25-loading-states
|
|
15
|
+
**Date:** 2026-08-03
|
|
16
|
+
**Project/Repo:** toga25-supply + toga-blox-npm (2.0)
|
|
17
|
+
**Task:** Implement the "Toga25-supply mockup" design's loading states — table skeletons (initial/refresh/filter), pagination loading, Refresh busy pill, bulk progress toast, and modal skeletons (cold/warm/sub-section) — pixel-faithful to the mockup, in toga25-supply with supporting toga-blox changes.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **TableSkeleton built on the real table's CSS module** — `src/components/TableSkeleton/TableSkeleton.tsx` imports `@agilant/toga-blox/dist/components/Table/themeConfig/toga.module.css` (the exact module the blox PrimaryTable is styled by), so skeleton and loaded table are pixel-identical by construction. Real header labels + static sort glyphs from table meta, type-aware cells (status dot, avatar circle, image tile + label bar, badge pill), real pagination chrome. **User visually approved.** Wired into `PrimaryTableServerLayout` replacing blox `TableShimmer`.
|
|
23
|
+
- **Shimmer texture matched to design** — `Shimmer.module.css` overlay rewritten to the mockup gradient: 100deg, transparent→38%, `#F7F8FB` at 50% (strong: `#F0F2F6`), →62% transparent, 100vw-wide band, 2s linear. Fixes every skeleton app-wide (modals included).
|
|
24
|
+
- **Pagination loading** — blox `TablePagination` gained `loadingPage?: number | null` (threaded through `PrimaryTable` + server template + both types); shows 12px spinner + `Loading page N…` (`.paginationLoadingText`, #72757D/14px) in place of the count; `TableBody` dims to 0.55 (`.bodyDimmed`) while a page fetch is in flight; controls stay full-strength (pointer-events blocked only). App wrapper detects page fetch via new `page` prop vs `paginationMeta.page` (`isPageFetch`) — refresh/filter/sort still show the full skeleton (per design), page changes keep rows.
|
|
25
|
+
- **Refresh busy pill** — all 5 pages (SalesOrders, Items, Bundles, VendorItems, Inventory): icon spins 1.1s in fixed 20px box (`.refreshIconBox` in `src/index.css`), label flips to `Refreshing…`; `useIsFetching({queryKey:["table-data"], predicate: q => q.state.data !== undefined})` excludes cold loads.
|
|
26
|
+
- **Fidelity audit (agent) against mockup source** confirmed shimmer/spinner/copy exact and produced 14 fixes, all applied: no columnBorders on skeleton (loaded table has none), rows 16, sort glyph placeholder, avatar/image/badge shapes, active-filter chip stays in skeleton header (`filteredColumnSlugs` from `columnFilters`), bulk toast track 14px + `background-attachment: fixed` + cancel spacing.
|
|
27
|
+
- **Modal skeletons (sales-order modal)** — cold = existing `SalesOrderModalSkeleton` (header shimmer); warm = new `warmHeader` prop renders real `SalesOrderTopBar` from the clicked row while body shimmers; row context plumbed as optional `row` on both wrappers' `renderModal` (server + client), passed as `rowRecord` from SalesOrders.tsx. Sub-section refresh = `SubSectionShimmer` (3×36px r6 bars) in Invoices/Shipments sections while their independent queries (`useInvoices`/`useShipments`) load — sections keep frame+title, no longer pop in late; `getDetailSections` show-gates include loading.
|
|
28
|
+
- **CodeRabbit fixes** — client wrapper's `renderModal` moved OUTSIDE the loading gate (open modal no longer unmounts on table refetch); shared `TableRowRecord = { uuid: string } & Record<string, unknown>` exported from `PrimaryTableServerLayout/types.ts`, reused in both wrappers + modal, `displayRecords` tightened to it (killed 2 pre-existing no-explicit-any errors).
|
|
29
|
+
- **Components added**: `BulkProgressToast` (design-exact, unwired — no bulk feature exists), `SubSectionShimmer`; barrel exports updated.
|
|
30
|
+
- **Client testing infra** — hosts entries added via UAC-elevated PowerShell (`Start-Process powershell -Verb RunAs`) for `compasscanada.dev.sandbox.togasupply` and `nychh.dev.sandbox.togasupply`; NYCHH is the inventory client (only one with dedicated `NYCHH_INVENTORY_GROUPINGS`). All dev servers closed at session end.
|
|
31
|
+
- Every round verified with `npx tsc --noEmit` + eslint (touched files) + `npm run build` — all clean at session end.
|
|
32
|
+
|
|
33
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
34
|
+
- **Hand-rolled skeleton from mockup geometry + big-bang parallel implementation.** First attempt (3 parallel agents: components + table wiring + modal wiring incl. error states) rendered a skeleton with no card border, no header labels, equal-width bars, bright white glint — user: "further off than we started" and **reverted everything** (`git checkout` + clean). Do not rebuild loading UI from the mockup's px values directly; mirror the app's real rendered table (shared CSS module) and ship ONE state at a time for visual sign-off.
|
|
35
|
+
- **`background-attachment: fixed` for the unison shimmer inside modals** — breaks inside the record modal's transform (treated as local). Use the per-element 100vw translateX overlay with the same gradient instead (deliberate, documented in `Shimmer.module.css`). It IS fine for `BulkProgressToast`'s progress fill (not inside a transform).
|
|
36
|
+
- **Rebuilding toga-blox while dev servers run** — `prebuild rm -rf dist` fails with "Directory not empty" (Windows file locks from Vite dep-optimizer). Stop all vite servers first, then `cd /c/WWW/toga-blox-npm && npm run build`, then restart (and `rm -rf node_modules/.vite` in the app if the browser shows stale-dep errors).
|
|
37
|
+
- **Starting a client dev server without its hosts entry** — vite resolves `--host` via DNS at startup: `Error: getaddrinfo ENOTFOUND compasscanada.dev.sandbox.togasupply`. The hosts entry must exist BEFORE `npm run <client>`. Binding 127.0.0.1 instead is NOT a workaround: the client slug comes from the first hostname segment at login.
|
|
38
|
+
- **Background Bash tasks inherit `c:\WWW` cwd, not the repo** — `npm run compasscanada` failed "Missing script"; always `cd /c/WWW/toga25-supply && npm run …` in background commands. Foreground shell cwd also resets between some calls.
|
|
39
|
+
- **`TaskStop` on an `npm run` vite server orphans the node child** holding the port — after stopping, kill listeners: `netstat -ano | grep LISTENING | grep ":517"` → `taskkill //F //PID <pid>`.
|
|
40
|
+
- **Blanket sed for `page,` insertion** hit the wrong block in `useItemFulfillmentModalViewModel.tsx` (duplicated `page` inside a destructure = syntax error). Targeted Edit with unique context fixed it.
|
|
41
|
+
- **`useIsFetching` without a predicate** made Refresh show "Refreshing…" during cold loads (design shows it only for refetches of on-screen data).
|
|
42
|
+
|
|
43
|
+
## Not tried yet (candidates for next session)
|
|
44
|
+
- **Table error states** (server/timeout/empty/no-results/permission/maintenance/ratelimit per mockup ErrorBlock) — spec extracted at scratchpad `table-states-spec.md` §2.12-2.14/§4; first attempt reverted; needs the app-side error surface (`useTableData` discards query errors — an app hook subscribing to the query cache by `["table-data", slug]` prefix worked in the reverted round and can be reused conceptually).
|
|
45
|
+
- **Modal error states** (generic/not-found/permission/conflict per `modal-states-spec.md` §§7-10) — `src/components/RecordLoadError` exists (unused, generic copy matches mockup); six placeholder `<>Error</>` sites mapped in the reverted round's integration map.
|
|
46
|
+
- **Warm open for Item/VendorItem/Bundle record modals** — same two-step as sales-order: accept `rowRecord`, hand real header to skeleton via `warmHeader`-style prop.
|
|
47
|
+
- **TableSkeleton for `PrimaryTableClientLayout`** (modal-embedded tables still use blox `TableShimmer`; also its skin is `supply-modal-table`, different tokens).
|
|
48
|
+
- **Bulk feature wiring** — `BulkProgressToast` has zero consumers; per-row processing scrim (100deg `rgba(63,93,202,0.06)` at 50%) not implemented; app has no bulk selection at all.
|
|
49
|
+
- **Class-B inherited chrome differences (USER DECISION PENDING)** — app table vs mockup: header ~38px/14px vs 34px/13px, rows ~40px vs 48px, cell padding 25px vs 12px, separators #D4D5D8 vs #E8E9EA, "per page" vs "rows shown", filter chip #4f6bd4/r2 vs #3F5DCA/r5 (looks like a blox bug), card shadow. Fixing = restyling the live table in toga-blox.
|
|
50
|
+
- **blox bug**: `.tableAndPaginationInner.supply` sets `font-size: var(--primaryTable-pagination-text-default)` which resolves to a COLOR (#8C8E96) — declaration silently dropped, text inherits ~16px.
|
|
51
|
+
|
|
52
|
+
## Current file state
|
|
53
|
+
| File | Status | Notes |
|
|
54
|
+
|------|--------|-------|
|
|
55
|
+
| toga25-supply `src/components/TableSkeleton/` | new | CSS-module-mirroring table skeleton (approved) |
|
|
56
|
+
| toga25-supply `src/components/BulkProgressToast/` | new | design-exact, unwired |
|
|
57
|
+
| toga25-supply `src/components/SubSectionShimmer/` | new | 3×36px bars sub-section well |
|
|
58
|
+
| toga25-supply `src/components/Shimmer/Shimmer.module.css` | modified | 100deg subtle band per design |
|
|
59
|
+
| toga25-supply `src/components/index.ts` | modified | new barrel exports |
|
|
60
|
+
| toga25-supply `src/layout/PrimaryTableServerLayout/{.tsx,types.ts}` | modified | TableSkeleton gate, `page`/isPageFetch, `loadingPage`, `filteredColumnSlugs`, `renderModal.row`, `TableRowRecord` |
|
|
61
|
+
| toga25-supply `src/layout/PrimaryTableClientLayout/{.tsx,types.ts}` | modified | `renderModal` outside gate + row context; still blox TableShimmer for loading |
|
|
62
|
+
| toga25-supply 6 table layouts + 2 VMs | modified | pass `page` through (SalesOrders/Items/Bundles/VendorItems/Generic/ItemFulfillment) |
|
|
63
|
+
| toga25-supply 5 pages | modified | Refresh busy pill + predicate |
|
|
64
|
+
| toga25-supply `src/index.css` | modified | `.refreshIconBox` + 1.1s spin keyframes |
|
|
65
|
+
| toga25-supply SalesOrderRecordModalLayout + view/VM/sections/skeleton/helpers | modified | warm/cold/sub-section modal loading states |
|
|
66
|
+
| toga-blox-npm (branch `feature-new-table`) `TablePagination.tsx` + `types.ts` | modified | loadingPage indicator, full-strength controls |
|
|
67
|
+
| toga-blox-npm `PrimaryTable.tsx` + `Table/types.ts` | modified | loadingPage threading, bodyDimmed on TableBody |
|
|
68
|
+
| toga-blox-npm `toga.module.css` | modified | `.paginationSpinner`, `.paginationLoadingText`, `.bodyDimmed` |
|
|
69
|
+
| toga-blox-npm `dist/` | rebuilt | current with all source changes |
|
|
70
|
+
| **Both repos: all changes UNCOMMITTED** (toga25-supply on `phase-two`) | — | user decides when/how to land |
|
|
71
|
+
|
|
72
|
+
## Decisions made
|
|
73
|
+
- **Skeletons mirror the real rendered app table, not the mockup's raw geometry** — share the blox CSS module so load→loaded never shifts; the mockup's own px values only apply where the app matches them. Chosen after the hand-rolled attempt was reverted. Rejected: patching blox `TableShimmer` (8 hardcoded columns, Tailwind-JIT-fragile).
|
|
74
|
+
- **One state at a time with user visual sign-off** — replaced the 3-slice parallel big-bang that got reverted.
|
|
75
|
+
- **Per-element translateX shimmer overlay** (viewport-anchored gradient breaks in transformed modals); exact mockup gradient stops preserved.
|
|
76
|
+
- **Page-fetch detection at the wrapper** via requested `page` vs `paginationMeta.page` — no blox useTableData changes needed.
|
|
77
|
+
- **Loading-state-only scope** — explicitly did NOT change Refresh button idle chrome (`secondaryActionAlt` stays, though mockup pill is blue), nor any class-B live-table styling; user instruction.
|
|
78
|
+
- **`TableRowRecord` typed with `unknown` not `any`**; alias lives in the server layout types (the "table-layout contract"), client types re-export.
|
|
79
|
+
- **Modal-embedded client tables render bare when empty** (no onboarding panel) — empty is normal there.
|
|
80
|
+
|
|
81
|
+
## Blockers
|
|
82
|
+
none — pending items are user decisions (class-B chrome; when to commit) and the next feature slices.
|
|
83
|
+
|
|
84
|
+
## Exact next step
|
|
85
|
+
> With DevTools throttling on, visually verify the three modal skeleton states in the sales-order modal (click a row = warm header; reload with the uuid in the URL = cold; watch Invoices/Shipments shimmer wells), then replicate the warm pattern to ItemRecordModalLayout, VendorItemRecordModalLayout, and BundleRecordModalLayout, and start the table error states from `table-states-spec.md` (scratchpad specs may be gone — re-extract from `Toga25-supply mockup/src/inventory-table.jsx` §ErrorBlock if so).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
_Saved by /session-save on 2026-08-03_
|
package/package.json
CHANGED