toga-ai 1.0.1101 → 1.0.1102

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.
@@ -37,6 +37,7 @@
37
37
  | [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time webhook importer that writes NetSuite sale records (invoice/cashSale/creditMemo/cashRefund/journalEntry) into `Forecast.Sales`; open before touching s |
38
38
  | [isFulfillable Propagation Up the SO↔PO Chain](features/fulfillable-item-propagation.md) | api2 interceptor that propagates `Items.isFulfillable` UP the SO↔PO chain onto client-facing item rows; open when the storefront Qty-Fulfilled gate is wrong or |
39
39
  | [Global search (GET /records/search — tiered, per-client, data-configured fields)](features/global-search.md) | Platform (all 2.0 apps) header search across record types; open when making a field searchable, tuning ranking, or a search is slow or misses results. |
40
+ | [HubSpot API Client (_Component_Api_Hubspot) — blog + LinkedIn content for togatech.com](features/hubspot-api-client.md) | `_Component_Api_Hubspot`: the one place 2.0 talks to HubSpot; serves the togatech.com blog (`campaigns/blogPosts`, `blogPost`, with `tagNames`) and LinkedIn pos |
40
41
  | [ItemClasses / _Model_Client_ItemClass — who actually uses it (and why it is not free)](features/item-classification-itemclasses.md) | `ItemClasses` is a near-unused per-client lookup that 2 of 36 clients already repurpose (Northwell service categories, Prudential device tiers); open before reu |
41
42
  | [Item-Fulfillment Stage Lifecycle (picked/packed/shipped) & Order Status](features/item-fulfillment-stage-lifecycle-and-order-status.md) | Item-fulfillment stage lifecycle (picked→packed→shipped) driving the calculated order `_status`, with per-client status policies; open when working order status |
42
43
  | [Location, Unit (asset), Ticket Department — notes + files tables, LocationTypes icons](features/location-unit-department-notes-files.md) | Shared Client-DB notes/files child tables for Locations, Units (assets) and TicketDepartments, plus LocationTypes icon columns; open before adding notes/files t |
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: HubSpot API Client (_Component_Api_Hubspot) — blog + LinkedIn content for togatech.com
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-20
10
+ owners: ["tcox"]
11
+ files:
12
+ - _underscore/Component/Api/Hubspot/Hubspot.php
13
+ - _underscore/Model/True/Campaign.php
14
+ related:
15
+ - apirequest-json-content-type.md
16
+ - ../../api2/features/record-scripts.md
17
+ - ../../../../standalone/apps/togatech/features/blog-page.md
18
+ - ../../../../standalone/apps/togatech/features/careers-paylocity-job-openings.md
19
+ ---
20
+
21
+ `_Component_Api_Hubspot`: the one place 2.0 talks to HubSpot; serves the togatech.com blog (`campaigns/blogPosts`, `blogPost`, with `tagNames`) and LinkedIn posts (`campaigns/socialPosts`); open before changing HubSpot calls, blog tags, or the token.
22
+
23
+ ## Summary
24
+
25
+ All HubSpot HTTP goes through `Component/Api/Hubspot/Hubspot.php` (code owner: Rohan, rgirish). `_Model_True_Campaign` (client True) exposes thin record-script entry points that api2 serves for the public website — same `campaigns` record as `campaigns/jobs` ([careers doc](../../../../standalone/apps/togatech/features/careers-paylocity-job-openings.md), [record-scripts](../../api2/features/record-scripts.md)):
26
+
27
+ | api2 route | Model method | Component method |
28
+ |---|---|---|
29
+ | `GET /v2/campaigns/socialPosts` | `socialPosts` | `getTogaLinkedInPosts` (TOGA LinkedIn company page, status SUCCESS only) |
30
+ | `GET /v2/campaigns/blogPosts` | `blogPosts` | `getPublishedBlogPosts` (state PUBLISHED, newest first, + `tagNames`) |
31
+ | `GET /v2/campaigns/blogPost` | `blogPost` | `getPublishedBlogPost` (numeric id required, + `tagNames`) |
32
+
33
+ ## How it works
34
+
35
+ - `get($route, $query)` = authenticated GET (`Bearer` token, `Accept` + `Content-Type: application/json`); throws with route + HTTP code when the body is not JSON. Limits are clamped by `sanitizeLimit` (default 20, max 100).
36
+ - Raw helpers `getBlogPosts` / `getBlogPost` return HubSpot's shape unchanged; the `getPublished*` wrappers add fields. Keep that split — add website fields in the wrappers, not the raw helpers.
37
+ - **`tagNames`** (added TRUE-82653): HubSpot posts carry only `tagIds`. `attachTagNames` adds `tagNames` (list, in `tagIds` order, unknown ids skipped). The website matches tags by **name** ([blog-page.md](../../../../standalone/apps/togatech/features/blog-page.md)).
38
+ - `getBlogTagNames()` pages `GET /cms/v3/blogs/tags` (limit 100, `after` cursor, max 10 pages = 1,000 tags; logs if cut off) → `id => name`.
39
+ - A failed tag lookup is logged (`HubSpot blog tag lookup failed`) and posts return **without** `tagNames` — tags never break the post list. Envelope unchanged.
40
+
41
+ ## Gotchas
42
+
43
+ - **⚠ The private-app token is a hardcoded `TOKEN` const in `Hubspot.php`** (committed). Needs scopes "content" (blog) and "social" (broadcasts). Rotating it = editing that const. Never copy the value anywhere else; never expose HubSpot to the browser.
44
+ - **⚠ `getPublishedBlogPost` does not check state** — `campaigns/blogPost` returns drafts by id. Fix before the togatech article page uses it (separate ticket).
45
+ - HubSpot CMS v3 returns **415** on a bodyless GET without a JSON `Content-Type` — `get()` sets it ([apirequest-json-content-type.md](apirequest-json-content-type.md)).
46
+ - `blogPosts` / `blogPost` cost one extra HubSpot call per request (the tag list). Add a cache if traffic grows.
47
+
48
+ ## Change history
49
+ - 2026-10-20 — Created; `getPublishedBlogPosts` / `getPublishedBlogPost` add `tagNames`, new `getBlogTagNames()` (TRUE-82653, tcox)
@@ -28,6 +28,7 @@
28
28
  | [View-as another user — parked admin tokens & return-to-self](features/view-as-session-tokens.md) | How an admin impersonates another user and gets back: the delegated token pair, the parked admin pair in localStorage, and the return path. |
29
29
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How toga2-commerce builds/deploys on AWS Amplify (one dynamic amplify.yml, branch-per-env, per-env blox channel); open to deploy, stand up an env, resolve a blo |
30
30
  | [Cart e2e — Cypress conventions & harness (toga2-commerce)](workflows/cypress-testing.md) | Cypress e2e conventions + the Cart harness (cartV2.cy.ts) for toga2-commerce; read before adding any spec here, and note it doubles as the config-cart refactor |
31
+ | [Testing edit-order mode locally (open any saved order in the cart)](workflows/edit-order-local-testing.md) | Manual test of Commerce edit-order mode on a real saved order (no new order needed); open when reproducing an edit-order bug locally. |
31
32
  | [Diagnosing ERR_HTTP2_PROTOCOL_ERROR (one client fails, everyone else is fine)](workflows/http2-protocol-error-diagnosis.md) | Diagnose ERR_HTTP2_PROTOCOL_ERROR when one client/network fails a togacommerce tenant but everyone else works (usually the client's TLS-inspection proxy, not ou |
32
33
  | [Running toga2-commerce locally (tenant hosts, not localhost)](workflows/local-development-hosts.md) | Commerce cannot run on `localhost`; use a tenant hostname in `/etc/hosts`. |
33
34
  | [Unit tests in toga2-commerce — vitest harness & the build-mode trap](workflows/unit-testing-vitest.md) | How to run and write unit tests in commerce (vitest + jsdom), and how to verify a change actually compiles without tripping the build-mode trap. |
@@ -6,7 +6,7 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-17
9
+ updated: 2026-10-21
10
10
  owners: ["bala", "tcox"]
11
11
  files:
12
12
  - src/pages/Cart/CartPage.tsx
@@ -39,7 +39,7 @@ On the cart "Notifications" section a user can add CC email addresses to an orde
39
39
  - `CartForm.tsx` — owns the duplicate UX. `handleAddEmailWithDuplicateCheck` wraps the passed-in `handleAddEmail`: it case-insensitively checks the existing `emails` list and, on a match, shows the message instead of adding. The **Add Email** button calls this wrapper.
40
40
  - `CartPage.tsx` — also owns the **Delegate Manager** lifecycle: `advancedSelectConfig.delegateManager.onSelect` / `.onClearInput`, the `lastDelegateManagerEmailRef` (useRef), the `removeDelegateManagerEmail` guard, the `watch("hasDelegateManager")` and `watch("delegateManager")` effects, and `handleClearUser`.
41
41
  - `EditCart.tsx` — cart-checkout mode. The order-for change effect (`onSuccess` of the user fetch) owns add **and** remove of the order-for user's emails, plus the local `ensureEmailSelected` helper and the `orderForUserChanged` flag.
42
- - `EditOrder.tsx` — edit-order mode. Still has the **original add-only** order-for pattern.
42
+ - `EditOrder.tsx` — edit-order mode. Seeds order-for emails through its own `ensureEmailSelected` (add-only — see below).
43
43
  - `useEmailOptionsStore.ts` — `addEmailOption` de-dupes the checkbox list; `removeEmailOption` removes case-insensitively; `selectEmailOption(email)` marks an option checked (idempotent — never toggles).
44
44
  - `useCartSalesQuoteZu.ts` — `addEmail` de-dupes and `removeEmail` removes from the actual `salesOrderEmailAddresses` payload, both case-insensitively.
45
45
 
@@ -58,6 +58,13 @@ Auto-added set per user: **their email**, `supervisorUser.email`, and `c_support
58
58
  3. Re-seed the new user's addresses through the local **`ensureEmailSelected(email)`** helper: `addEmailOption` → `selectEmailOption` → `addEmail`. Idempotent, so re-running it is safe.
59
59
  4. The seeding block runs when `salesOrderEmailAddresses` is empty **or** when `orderForUserChanged`. Before the fix it ran only on empty, so the second pick's emails were added **un-checked** and never reached the payload.
60
60
 
61
+ ### Order-for seeding in edit-order mode (`EditOrder.tsx`)
62
+ 1. The order-for effect depends on `orderForUser`, `cartData.cartItems`, `cartData.cartBundles` — it re-runs on **every cart change**.
63
+ 2. The email block runs only when the saved order has **0** `salesOrderEmailAddresses`, **or** `formValues.hasDelegateManager && formValues.delegateManager?.uuid`.
64
+ 3. In edit mode `hasDelegateManager` / `delegateManager` are set **only** from a saved `SalesOrderNote` with type slug `delegate` plus `salesOrder.approvalUser`. The field itself is hidden in edit mode by design (see [cart-order-for-user-and-approving-manager](cart-order-for-user-and-approving-manager.md)).
65
+ 4. It seeds the order-for user, `supervisorUser`, `c_supportedByUserId` and the logged-in user via `ensureEmailSelected` — idempotent, so the re-runs are safe.
66
+ 5. It **never removes** a previous order-for user's auto-added emails when order-for changes (open gap).
67
+
61
68
  ### Delegate Manager email lifecycle (`CartPage.tsx`, cart-checkout mode)
62
69
  "Associate" in the Compass UI = Delegate Manager. Its email is tracked by **`lastDelegateManagerEmailRef`** (a `useRef`), because the form value cannot be used (see Gotchas). The email is parsed out of the option label, which has the shape `"First Last (email)"`.
63
70
 
@@ -87,7 +94,8 @@ The de-dupe mechanism is uniform across clients. Only the message label text dif
87
94
  - **`CartFormSection` overwrites the form value BEFORE your handler runs.** It calls `field.onChange(value)` and *then* `config.onSelect(value, valueKey)` (same for `field.onChange(null)` before `config.onClearInput`). So `formMethods.getValues("delegateManager")` inside those handlers already holds the **new** value — you **cannot** read the previous pick from the form. Keep the previous value in a `useRef`.
88
95
  - **Read the previous order-for user from the store, not the closure.** Inside the user-fetch `onSuccess` the render closure's `selectedUser` is stale; use `useSelectedUserZu.getState().selectedUser`.
89
96
  - **`delegateManager` defaults to a truthy object** (`{uuid:"",name:""}`) now that it is a typed form field, so `if (formValues.delegateManager)` is always true. Check `formValues.delegateManager?.uuid` instead (3 call sites: `EditCart.tsx` ×2, `EditOrder.tsx` ×1).
90
- - **`EditOrder.tsx` (edit-order mode) still has the add-only order-for pattern** — the same wrong-recipient bug very likely exists there. Left alone deliberately: edit-order has different manager rules (`assignedTo`). Fix it as its own task.
97
+ - **`EditOrder.tsx` still does not remove the previous order-for user's emails** when order-for changes — the wrong-recipient bug very likely exists there. Edit-order has different manager rules (`assignedTo`); fix it as its own task. Its toggle bug is fixed (TRUE-82631, below).
98
+ - **Edit-order toggle bug (TRUE-82631, Compass):** EditOrder seeded with `handleEmailChange(email, false)`. Because the effect re-runs on every cart change, on orders placed with a delegate manager the manager/supervisor emails flipped to un-checked on load or on the next cart change and dropped out of the update payload. To reproduce or test, see [edit-order-local-testing](../workflows/edit-order-local-testing.md).
91
99
  - Normalize with `(x ?? "").trim().toLowerCase()`, not `x?.trim().toLowerCase()` — both are nullish-safe (optional chaining short-circuits the whole chain, it does not throw), but `(x ?? "")` guarantees a string and avoids an `undefined === undefined` match edge.
92
100
  - Adding the label to only some `CARTPAGE.ts` files leaves other client/role/language users with a blank message.
93
101
  - Do not use a toaster or `setError` (red) for this — product wants the sapphire info style.
@@ -99,3 +107,7 @@ The de-dupe mechanism is uniform across clients. Only the message label text dif
99
107
  - [cart-page-config-architecture](cart-page-config-architecture.md)
100
108
  - [client-fields](client-fields.md)
101
109
  - [cypress-testing](../workflows/cypress-testing.md) — e2e coverage that pins this behavior.
110
+ - [edit-order-local-testing](../workflows/edit-order-local-testing.md) — manual test of edit-order mode.
111
+
112
+ ## Change history
113
+ - 2026-10-21 — EditOrder seeds with idempotent `ensureEmailSelected` instead of toggling `handleEmailChange`; remove-on-change gap still open (TRUE-82631, commit `515e5752`, PR #375, not yet merged to `_production`). (tcox)
@@ -6,14 +6,15 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-06
10
- owners: ["bala"]
9
+ updated: 2026-10-21
10
+ owners: ["bala", "tcox"]
11
11
  files:
12
12
  - src/pages/Cart/api/CartApi.ts
13
13
  - src/pages/Cart/view/modals/NewUserForm.tsx
14
14
  - src/pages/Cart/CartPage.tsx
15
15
  - src/pages/Cart/helpers/checkIsProceedToCheckoutEnabled.ts
16
16
  - src/pages/Cart/view/cartForm/CartFormSection.tsx
17
+ - src/pages/Cart/view/cartForm/EditOrder.tsx
17
18
  - src/components/Inputs/AdvancedInput.tsx
18
19
  - src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts
19
20
  - src/pages/Cart/viewModel/FIELDS/*/NEWUSERFORMFIELDS.ts
@@ -54,6 +55,10 @@ For roles without search, the cart Order For list shows only the logged-in perso
54
55
  - The red `requiredMessage` under Delegate Manager uses the same `BaseText` style as other cart errors (`text-base`, `text-crimson-700`, `font-regular`). `AdvancedInput` takes optional `isInvalid` / `errorMessageId` → `aria-invalid` / `aria-describedby`; `BaseText` accepts `id`.
55
56
  - Delegate ticked but left empty on a user who HAS a supervisor is fine: the backend falls back to the user's own supervisor.
56
57
 
58
+ ### Edit-order mode
59
+ - `hasDelegateManager` and `delegateManager` are **hidden in edit mode by design**: `isHiddenDuringEditMode: true` in every Compass / Compass Canada `CARTPAGE.ts` (commit `cee68b49`, 2026-03-16, apeterson; related `1843535f` "Fix delegate manager during edit mode"). QA must not log the missing field as a bug.
60
+ - In edit mode the values come only from the saved order: a `SalesOrderNote` with type slug `delegate` + `salesOrder.approvalUser` (`EditOrder.tsx`). Email effect of this: [cart-notification-emails](./cart-notification-emails.md).
61
+
57
62
  ## Gotchas
58
63
 
59
64
  - **The duplicate check is ACL scoped.** `GET /v2/users` returns only users the caller's role can see, so it can still miss an existing user. Not a hard uniqueness guarantee; the DB has none either.
@@ -64,6 +69,7 @@ For roles without search, the cart Order For list shows only the logged-in perso
64
69
  - Typecheck with `npx tsc -b`, not `npx tsc --noEmit -p .` (see [amplify-build-and-deploy](../workflows/amplify-build-and-deploy.md)).
65
70
 
66
71
  ## Change history
72
+ - 2026-10-21 — Documented: delegate manager fields hidden in edit-order mode by design; edit-mode values come from the saved order (TRUE-82631). (tcox)
67
73
  - 2026-10-06 — Add New User checks username OR email; approving-manager rule (auto-tick delegate, checkout gated) driven by `requiredMessage` config; NewUserForm min height (TRUE-82423). (bala)
68
74
 
69
75
  ## Related
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Testing edit-order mode locally (open any saved order in the cart)
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-10-21
10
+ owners: ["tcox"]
11
+ files:
12
+ - src/pages/Cart/viewModel/useCartViewModel.ts
13
+ - src/pages/Cart/view/cartForm/EditOrder.tsx
14
+ - src/pages/Cart/view/cartTable/BundleItemQuantityControl.tsx
15
+ - package.json
16
+ related:
17
+ - ./local-development-hosts.md
18
+ - ./cypress-testing.md
19
+ - ../features/cart-notification-emails.md
20
+ - ../features/cart-order-for-user-and-approving-manager.md
21
+ ---
22
+
23
+ Manual test of Commerce edit-order mode on a real saved order (no new order needed); open when reproducing an edit-order bug locally.
24
+
25
+ ## Steps
26
+
27
+ 1. Point local Commerce at the environment that has the data — for Compass QA data that is client-sandbox: see [local-development-hosts](./local-development-hosts.md) step 3 (`npx vite --mode sandbox-client --host compass.togacommerce`, clear `localStorage` key `commerce`). Plain `npm run compass` hits beta, which has no reachable Compass DB through the TOGA DB tool.
28
+ 2. Pick a test order in that environment's `Client_<Tenant>` DB (read-only):
29
+ - placed with a delegate manager: `SalesOrders` joined to `SalesOrderNotes` → `SalesOrderNoteTypes` with slug `delegate`;
30
+ - or an order with no `SalesOrderEmailAddresses` rows.
31
+ 3. Open `/cart?uuid=<SalesOrders.uuid>`. `useCartViewModel` syncs that order into the cart and sets `inEditMode`. TOGa Supply links into edit-order the same way.
32
+ 4. To trigger a cart change (re-runs the `EditOrder` effects): change a quantity. A kit-only order has no quantity control on primary lines, so add a standalone item from the catalog, return through the cart icon (keeps `?uuid=`), then remove it.
33
+ 5. Check state on screen (e.g. notification checkboxes). **Do not save** unless the test needs it — saving writes to the shared client-sandbox order.
34
+
35
+ ## Gotchas
36
+
37
+ - **Kit primary lines show plain quantity text**: `BundleItemQuantityControl` returns no control when `slug === "primary"` or `!canEdit`.
38
+ - **No Delegate Manager field in edit mode** is by design for Compass, not a bug — see [cart-order-for-user-and-approving-manager](../features/cart-order-for-user-and-approving-manager.md).
39
+ - e2e edit-order setup (seeded localStorage keys) is different — see [cypress-testing](./cypress-testing.md).
40
+
41
+ ## Change history
42
+ - 2026-10-21 — Created while testing the EditOrder email toggle fix (TRUE-82631). (tcox)
@@ -6,7 +6,7 @@ project: TOGa Commerce
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-19
9
+ updated: 2026-10-21
10
10
  owners: [apeterson, tcox]
11
11
  files:
12
12
  - toga2-commerce/vite.config.ts
@@ -23,6 +23,8 @@ Commerce cannot run on `localhost`; use a tenant hostname in `/etc/hosts`. Open
23
23
 
24
24
  1. Add to `/etc/hosts`, pointing at `127.0.0.1`: `compass.togacommerce`, `compasscanada.togacommerce`, `quad.togacommerce` (these are the exact hosts allowed in `vite.config.ts`). For `npm run sandbox-client` also add `client.sandbox.togacommerce`.
25
25
  2. Start Vite and open the tenant host, never `http://localhost:<port>`.
26
+ 3. To run a tenant against **client-sandbox** (where QA tests and `Client_Compass` data lives), combine the mode and the tenant host: `npx vite --mode sandbox-client --host compass.togacommerce`. `npm run sandbox-client` uses host `client.sandbox.togacommerce` instead.
27
+ 4. After switching API target, clear the `localStorage` key `commerce` before signing in.
26
28
 
27
29
  ## Why localhost fails
28
30
 
@@ -35,6 +37,7 @@ The tenant is resolved from `window.location.hostname`, and api2 checks the requ
35
37
  - **⚠ `npm run dev` and the tenant scripts (`npm run compass`, …) talk to beta, not sandbox-client.** `.env.development` sets `VITE_API = https://api.beta.togahub.com/v2`. A DB error seen locally (e.g. 1054 `Unknown column 'isOpen'` on `SalesOrderItems`, 2026-10-08) is beta's schema — check the request host before reading another environment's DB or `Logs_<Tenant>.Api`.
36
38
 
37
39
  ## Change history
40
+ - 2026-10-21 — Added: run a tenant host against client-sandbox with `--mode sandbox-client` (TRUE-82631). (tcox)
38
41
  - 2026-10-19 — Tenant dev scripts (`npm run compass`) also hit beta. (tcox)
39
42
  - 2026-10-08 — Added: local dev API target is beta. (tcox)
40
43
  - 2026-09-25 — Documented after tracing "cannot sign in locally" to the Origin/`Core.Domains` check. (apeterson)
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
 
22
22
  ## 2.0 framework
23
23
 
24
- - **_underscore** (_Underscore) _(framework core)_ — 113 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
24
+ - **_underscore** (_Underscore) _(framework core)_ — 114 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
25
25
  - **worker2** (Worker) — 92 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
26
26
  - **api2** (API) — 27 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
27
27
  - **dbchanges2** (Database Changes) _(framework core)_ — 23 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
@@ -30,7 +30,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
30
30
  - **apiproxy** (API Proxy) — 1 doc(s) → [2.0/apps/apiproxy/INDEX.md](2.0/apps/apiproxy/INDEX.md)
31
31
  - **toga2-view** (TOGa View Frontend) — 12 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
32
32
  - **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
33
- - **toga2-commerce** (TOGa Commerce) — 33 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
33
+ - **toga2-commerce** (TOGa Commerce) — 34 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
34
34
  - **toga25-supply** (TOGa 2.5 Supply) — 27 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
35
35
  - **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
36
36
  - **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
@@ -39,7 +39,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
39
39
 
40
40
  ## standalone framework
41
41
 
42
- - **togatech** (TOGA Technology Website) — 11 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
42
+ - **togatech** (TOGA Technology Website) — 12 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
43
43
  - **websocket** (WebSocket Server) — 2 doc(s) → [standalone/apps/websocket/INDEX.md](standalone/apps/websocket/INDEX.md)
44
44
  - **talos** (TOGa Talos Chat) — 3 doc(s) → [standalone/apps/talos/INDEX.md](standalone/apps/talos/INDEX.md)
45
45
  - **talos-backend** (TOGa Talos) — 33 doc(s) → [standalone/apps/talos-backend/INDEX.md](standalone/apps/talos-backend/INDEX.md)
@@ -3,6 +3,7 @@
3
3
  | Doc | Summary |
4
4
  |-----|---------|
5
5
  | [TOGA Technology Website Architecture](architecture.md) | Public TOGA Technology marketing site — standalone React/TS SPA (no PHP framework); open for stack, bootstrap, routing, page layout, integrations, build/deploy. |
6
+ | [Blog Page (/blog) — HubSpot posts via api2, tag names, generated card art](features/blog-page.md) | togatech `/blog` home + topic views + search, ported from the designer's `design-blog/` export; posts from HubSpot only through api2 `campaigns/blogPosts`, tags |
6
7
  | [Careers Section — Live Paylocity Job Openings (About page + /v2/campaigns/jobs proxy)](features/careers-paylocity-job-openings.md) | togatech About page "Join Our Team" section — live Paylocity jobs via unauthenticated `GET /v2/campaigns/jobs` record-script proxy; open for the fetch→proxy→map |
7
8
  | [NavV2 Global Nav — menus, panel shell, scroll reveal, Talos FAB](features/navv2-global-nav.md) | togatech NavV2 header (designer "phase one"): data-driven menus, one shared panel shell + WebGL canvas, `data-nav-reveal` scroll hide/show, opt-in `data-nav-pin |
8
9
  | [Platform Product Page (/our-platform) — ported designer prototype](features/platform-product-page.md) | togatech `/our-platform` TOGa Platform product page, ported from designer prototype `our-platform-v5`; open for the page-root-as-`<html>` rule, effect-order rul |
@@ -11,4 +12,4 @@
11
12
  | [Talos Product Page (/talos) — ported designer prototype](features/talos-product-page.md) | togatech `/talos` Talos AI product page, ported from a designer HTML/JSX/CSS prototype; open for its talos.css port edits, section notes, open port work and pla |
12
13
  | [Creating Pull Requests on togatech](workflows/creating-pull-requests.md) | Open a PR on `agilantsolutions/togatech` — `gh` CLI lacks `read:org`, so use a raw GitHub REST call with the keychain token; base is `_production`. |
13
14
  | [Porting a Designer Prototype to a togatech Page](workflows/designer-prototype-port.md) | togatech procedure for turning a designer HTML/JSX/CSS prototype into a live page (`/talos`, `/our-platform`): where drops and port tools live, generated prefix |
14
- | [Running togatech Locally & Verifying a Page Without Cypress](workflows/local-dev-and-headless-verification.md) | Get togatech running on a dev machine and verify a data-driven section without Cypress — `npm install` first, `lint` is dead, Cypress 15 fails on Windows. |
15
+ | [Running togatech Locally & Verifying a Page Without Cypress](workflows/local-dev-and-headless-verification.md) | Get togatech running on a dev machine and verify a page — `npm install` first, `lint` is dead, unset `ELECTRON_RUN_AS_NODE` for Cypress, Puppeteer fallback. |
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: Blog Page (/blog) — HubSpot posts via api2, tag names, generated card art
3
+ framework: "standalone"
4
+ repo: togatech
5
+ project: TOGA Technology Website
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-20
10
+ owners: ["tcox"]
11
+ files:
12
+ - togatech/src/pages/Blog/BlogPage.tsx
13
+ - togatech/src/pages/Blog/blog.css
14
+ - togatech/src/lib/hubspot.tsx
15
+ - togatech/src/pages/Blog/viewModel/blogCards.ts
16
+ - togatech/src/pages/Blog/viewModel/useBlogCards.ts
17
+ - togatech/src/pages/Blog/viewModel/blogTags.ts
18
+ - togatech/src/pages/Blog/viewModel/blogArt.ts
19
+ - togatech/src/pages/Blog/viewModel/useSearchKeyLabel.ts
20
+ - togatech/src/pages/Blog/viewModel/FIELDS/BLOGPAGEFIELDS.json
21
+ - togatech/src/pages/Blog/vendor/toga-thumbs.js
22
+ - togatech/src/components/SpaceSky/spaceSky.ts
23
+ - togatech/cypress/e2e/blogPage/blogPage.cy.ts
24
+ related:
25
+ - ../architecture.md
26
+ - ../workflows/designer-prototype-port.md
27
+ - seo-aeo-geo-prerender.md
28
+ - navv2-global-nav.md
29
+ - ../../../../2.0/apps/_underscore/features/hubspot-api-client.md
30
+ ---
31
+
32
+ togatech `/blog` home + topic views + search, ported from the designer's `design-blog/` export; posts from HubSpot only through api2 `campaigns/blogPosts`, tags matched by name, generated card art; open before touching the blog, its tags, or any HubSpot data on the site.
33
+
34
+ ## Summary
35
+
36
+ `/blog` (`src/pages/Blog/`, TRUE-82653, first iteration) is a port of `design-blog/TOGA Blog.html` (blog-home-v11 + blog-dupe.css v72 + toga-intro) — method in [designer-prototype-port.md](../workflows/designer-prototype-port.md). Scope: home, topic views at `/blog#topic-<name>`, search modal (`/` and Cmd/Ctrl+K), intro animation. Cards link to the live HubSpot post url (https only); the article page is a separate ticket (the old `BlogPostPage.tsx` is unrouted). Code-split + prerendered (ready selector `[data-testid="blog-page"]`), in AppLayout `ROUTES_WITH_OWN_FOOTER`, nav link live with `highlightCurrent` ([navv2-global-nav.md](navv2-global-nav.md)).
37
+
38
+ ## How it works
39
+
40
+ **Data (HubSpot only through api2):**
41
+ 1. `src/lib/hubspot.tsx fetchBlogPosts(limit)` → toga-blox `apiGet('campaigns/blogPosts?limit=')` → `envelope.data.campaigns.blogPosts`; `success: false` throws. Server side: [_underscore hubspot-api-client](../../../../2.0/apps/_underscore/features/hubspot-api-client.md).
42
+ 2. One adapter `viewModel/blogCards.ts toBlogCards` maps raw posts to slim cards.
43
+ 3. `useBlogCards`: React Query key `["blogCards", 2, limit]`, staleTime 5 min; only the slim cards are cached (the cache is persisted to localStorage). The query is off during prerender, so the first client render equals the snapshot.
44
+ - **Decided:** never call `api.hubapi.com` from the browser and never put a HubSpot token in a `VITE_*` var — Vite inlines it into the public bundle. The browser HubSpot client, the `/hubspot-api` vite proxy and `VITE_HUBSPOT_SERVICE_KEY` were removed.
45
+
46
+ **Tags are matched by NAME, not id** (tcox + CTO: never come back to this). `viewModel/blogTags.ts`:
47
+ - Normalize: lowercase, `-` / `_` = space.
48
+ - Known names: topic keys (AI, Services, Security, Platform, Company); service areas (Lifecycle, Managed, Tech sales, True Partner, Advisory, plus their long names); types (Guide, Insight, Customer story, News + plurals); `Featured`.
49
+ - Other spellings: add to `tags.aliases` in `BLOGPAGEFIELDS.json` (data-only edit, no code).
50
+ - First match in FIELDS order wins. An area alone implies topic Services. Unknown names warn in the dev console only.
51
+
52
+ **Card art** = the designer's generated art (`vendor/toga-thumbs.js`, shipped unchanged; `viewModel/blogArt.ts` `withArt` / `artSrc`), not HubSpot featured images (tcox's call).
53
+ - Color = the post's topic; Services posts use their service area. Map in `BLOGPAGEFIELDS.json` `art`: Security honey/gold, AI T-linear gradient, Platform spectrum, Company peach, Lifecycle cobalt + aqua glow, Managed mint, Tech sales fuchsia, Advisory lilac, True Partner peach. No topic → Lifecycle (`art.fallback`).
54
+ - Diagram picked from the title, stepped so neighbours with the same art never repeat.
55
+
56
+ **Search key hint** (`useSearchKeyLabel.ts`): "⌘K" on Apple devices, "Ctrl K" elsewhere (`header.searchKeyMac` / `searchKeyOther`). First render uses the non-Apple label (matches the prerender), then switches in a layout effect.
57
+
58
+ **Intro + sky:**
59
+ - `components/SpaceSky/` is a TS port of the designer's starfield: `createSpaceSky(cv, look)`, optional `look` prop — reusable on other pages.
60
+ - Intro scripts are vendored unchanged in `public/assets/blog/toga-intro/` and loaded by script tags. An inline snippet in `index.html` shows the intro start screen on a `/blog` load; it is skipped for `#topic-` links, back/forward, reduced motion and the prerender user agent.
61
+
62
+ **CSS:** `blog.css` is generated by `design-blog/_port-tools/build-blog-css.cjs` (local only), scoped `.blogPage`. Never bulk hand-edit it.
63
+
64
+ **Tests:** `cypress/e2e/blogPage/blogPage.cy.ts` (9 specs) stubs `POST **/auth/public` and `GET **/campaigns/blogPosts*` with `cypress/fixtures/blogPage/blogPosts.json`.
65
+
66
+ ## Gotchas
67
+
68
+ - **⚠ HubSpot has almost no tags yet.** Checked 2026-10-20: the portal has 2 tags ("Insights", "Innovation"); 5 of 6 posts are untagged, so most cards land on the fallback topic. Marketing must create and use the topic / area / type tag names above.
69
+ - **⚠ The old `VITE_HUBSPOT_SERVICE_KEY` is still in the production `.env` and in git history** — it must be rotated (see [talos-chat-widget.md](talos-chat-widget.md) Gotchas).
70
+ - `campaigns/blogPost` (single post) also returns drafts — fix before the article page uses it (separate ticket; [hubspot-api-client](../../../../2.0/apps/_underscore/features/hubspot-api-client.md)).
71
+ - Hydration error #418 on load is the pre-existing site-wide mismatch ([seo-aeo-geo-prerender.md](seo-aeo-geo-prerender.md) Gotchas), not the blog.
72
+ - Running Cypress / route args from Claude's shell: [local-dev-and-headless-verification.md](../workflows/local-dev-and-headless-verification.md).
73
+
74
+ ## Change history
75
+ - 2026-10-20 — Created: /blog home + topics + search ported from design-blog; HubSpot via api2 only (browser client, proxy and VITE key removed); tags by name; generated card art (TRUE-82653, tcox)
@@ -6,7 +6,7 @@ project: TOGA Technology Website
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-15
9
+ updated: 2026-10-20
10
10
  owners: ["tcox"]
11
11
  files:
12
12
  - togatech/src/components/NavV2/view/NavV2.tsx
@@ -39,7 +39,7 @@ Global header in `src/components/NavV2/` (MVVM: `view/`, `viewModel/`, menu data
39
39
 
40
40
  **Menus (data-driven):**
41
41
  - Two menus shown: What we do, Company. Industries / Resources are hidden in `NAVFIELDS.json`; their panel code is kept for a later phase — re-enable via data, do not rebuild.
42
- - Flat links are `flatLinks[]` (was single `flatLink`): The TOGA Blog → `/blog` stub, Contact.
42
+ - Flat links are `flatLinks[]` (`FlatLink` in `types.ts`): The TOGA Blog → `/blog` (live), Contact. Every flat link gets `aria-current="page"` on its page; `highlightCurrent: true` in `NAVFIELDS.json` also turns it white (`.flatLinkCurrent`) — set on the blog link only.
43
43
  - What we do has 2 tabs (Business Units, Platforms). Tab hover switches the tab but never moves keyboard focus (`useRovingTabs`).
44
44
  - Company panel and What-we-do share `panels/PanelFooter.tsx`.
45
45
  - Platforms tab titles ("Talos AI", "TOGa Platform") show a brand logo on the left: data is `logo: {src, size}` in `NAVFIELDS.json` (`PlatformLogo` in `types.ts`), files in `public/assets/nav/` (Talos 38px, TOGa Platform 32px), 9px gap, class `.platformTitle`.
@@ -80,6 +80,7 @@ Global header in `src/components/NavV2/` (MVVM: `view/`, `viewModel/`, menu data
80
80
  - Nav type changes are checked against the "Mega-menu type spec" PDF (TRUE-82042), not the older `navigation-type-spec.md` (TRUE-82043).
81
81
 
82
82
  ## Change history
83
+ - 2026-10-20 — /blog flat link live; `highlightCurrent` flag + `aria-current` on flat links (TRUE-82653, tcox)
83
84
  - 2026-10-15 — Matched the "Mega-menu type spec" PDF: nav base type 400 / 1.5 / #fff, unstated line-heights inherit 1.5, trigger buttons stay normal; /our-platform pinned scenes; TalosLauncher pages hide the FAB (TRUE-82042 / TRUE-82652, tcox)
84
85
  - 2026-10-12 — Type spec match, Platforms logos, one shared panel shell + SilkCanvas (WebGL context leak fix), MobileNav closes only above 1280px (TRUE-82042, tcox)
85
86
  - 2026-10-05 — Phase-one redesign: 4 → 2 menus, `flatLinks[]`, Platforms/Company panels rebuilt, scroll reveal + `data-nav-pinned`, compact viewport, FAB visibility rules (tcox)
@@ -6,8 +6,8 @@ project: TOGA Technology Website
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-10-19
10
- owners: [akhokhani]
9
+ updated: 2026-10-20
10
+ owners: [akhokhani, tcox]
11
11
  files:
12
12
  - togatech/src/lib/talosSession.ts
13
13
  - togatech/src/lib/talosRecaptcha.ts
@@ -38,7 +38,8 @@ The site chat talks to Talos through a public two-step flow: `POST /api/ai/talos
38
38
  ## Gotchas
39
39
 
40
40
  - **⚠ `sendEmail` is callable by anyone.** Its endpoint, template uuid and recipients all sit in the public JS bundle, and the server checks no captcha, so a bot can call it directly. The Talos session checks cannot protect it. `Footer.tsx` and `ContactForm.tsx` generate a `recaptchaToken`, but `useSendEmail` **drops it** because it only forwards `fieldMapping` fields. Open TogaHub/PHP follow-up: verify reCAPTCHA in `sendEmail` (actions `footer_form_submit`, `contact_form_submit`, `talos_lead`; hostname `togatech.com`; score ≥ 0.5), pin recipients per template and ignore public `to`/`cc`, rate-limit, ideally switch to POST, and exempt authenticated service callers (Talos sends reminder emails through it).
41
- - **⚠ `VITE_HUBSPOT_SERVICE_KEY` (a HubSpot private-app token) is compiled into the public bundle.** Any `VITE_*` value ships to the browser. It should move server-side.
41
+ Handoff sent to Rohan 2026-10-20 (POST, server-side recipients, siteverify score ≥ 0.5, secret key in server env only); separate ticket.
42
+ - **⚠ Rotate the old HubSpot token.** `VITE_HUBSPOT_SERVICE_KEY` was removed from the code (TRUE-82653 — HubSpot now goes through api2, [blog-page.md](blog-page.md)), but the value is still in the production `.env` and in git history. Any `VITE_*` value ships in the public bundle — never put a secret there.
42
43
  - **The page loads two reCAPTCHA release scripts.** Calling `grecaptcha.execute` from an isolated or extension context (for example a test harness that injects a script) throws `auto_render_clients` errors. Code that runs in the page context works, so test token minting through the app itself.
43
44
  - **Low scores from automation are normal.** An automated or extension-driven browser scored 0.3 (0.9 later in the same browser). Do not take a single test run as the real-user score.
44
45
  - **ESLint cannot run in this repo** (a legacy `.eslintrc` with ESLint 9). Use `tsc --noEmit` as the working check.
@@ -50,4 +51,5 @@ The site chat talks to Talos through a public two-step flow: `POST /api/ai/talos
50
51
  - [talos-product-page](talos-product-page.md) · [architecture](../architecture.md)
51
52
 
52
53
  ## Change history
54
+ - 2026-10-20 — HubSpot VITE key removed from code (rotation still open); sendEmail lockdown handed to Rohan (TRUE-82653, tcox)
53
55
  - 2026-10-19 — created: session mint now sends a reCAPTCHA v3 token (PR #44); recorded the open `sendEmail` and HubSpot-key exposure (akhokhani)
@@ -6,7 +6,7 @@ project: TOGA Technology Website
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-15
9
+ updated: 2026-10-20
10
10
  owners: ["tcox"]
11
11
  files:
12
12
  - togatech/src/utils/hooks/useRootStyles.ts
@@ -30,16 +30,16 @@ togatech procedure for turning a designer HTML/JSX/CSS prototype into a live pag
30
30
 
31
31
  ## Summary
32
32
 
33
- The designer ships static HTML + in-browser JSX + CSS prototypes. Each redesigned page is a port of one (`/talos` → [talos-product-page.md](../features/talos-product-page.md), `/our-platform` → [platform-product-page.md](../features/platform-product-page.md)). The method below is shared; page-specific rules live in each page doc.
33
+ The designer ships static HTML + in-browser JSX + CSS prototypes. Each redesigned page is a port of one (`/talos` → [talos-product-page.md](../features/talos-product-page.md), `/our-platform` → [platform-product-page.md](../features/platform-product-page.md), `/blog` → [blog-page.md](../features/blog-page.md)). The method below is shared; page-specific rules live in each page doc.
34
34
 
35
35
  ## Steps
36
36
 
37
- 1. **Get the drop.** Drops live in `togatech/design-files/` — local only, excluded via `.git/info/exclude`, never committed. Current prototype: `design-files/uploads/New TOGA WEBSITE/TOGA Website/`. Port tools live in `design-files/_port-tools/` (also local only): `build-<page>-css.cjs`, `design-server.cjs` (serves the design on `localhost:8765`), `compare.cjs` (design vs port screenshots side by side), `hydration-check.cjs` (serves `dist/`, reports hydration errors per prerendered route), `perf.cjs`, `text-diff.cjs`. If a tool is lost, re-create it before the next design change.
37
+ 1. **Get the drop.** Drops live in `togatech/design-files/` and `togatech/design-blog/` (blog export) — local only, excluded via `.git/info/exclude`, never committed. Current prototype: `design-files/uploads/New TOGA WEBSITE/TOGA Website/`. Port tools live in each drop's `_port-tools/` (also local only; blog: `design-blog/_port-tools/build-blog-css.cjs`): `build-<page>-css.cjs`, `design-server.cjs` (serves the design on `localhost:8765`), `compare.cjs` (design vs port screenshots side by side), `hydration-check.cjs` (serves `dist/`, reports hydration errors per prerendered route), `perf.cjs`, `text-diff.cjs`. If a tool is lost, re-create it before the next design change.
38
38
  2. **Generate the page CSS — never hand-port it.** A postcss script concatenates the design CSS files **in the HTML's load order** into one plain file (`talos.css`, `ourPlatform.css`), prefixes every selector with the page root class (`.talosPage`, `.platformPage`), maps `:root` / `html` / `body` onto the root, rewrites asset urls to `/assets/<page>/`, keeps keyframe names. Port fixes go in the script's `EDITS` list (each asserts its exact match count, so a new drop that moves the text fails loudly) or its appended "port additions". Regenerate; never bulk hand-edit the output.
39
39
  - Plain CSS, **not a CSS module**: design JS finds elements by raw class names/ids and sets keyframe names inline; hashing breaks that (CTO-reviewed).
40
40
  3. **Make the page root stand in for `<html>`.** Whatever the design writes on `document.documentElement` (attributes, custom properties) goes on the page root, since the CSS maps `html` there. Viewport reads stay on `documentElement`. Render constant root vars in JSX `style` (prerender-safe). The root needs `isolation: isolate` so low/negative `z-index` backdrop layers paint above its background.
41
41
  - Real `<html>` styles (`overflow-x: clip` — never `hidden`, it breaks `position: sticky`; `scroll-behavior: smooth`) go through `useRootStyles(MODULE_CONST)`: layout effect, skips smooth scroll under `prefers-reduced-motion`, and **removes** the properties on unmount (never "restores" — a prerendered snapshot already carries them).
42
- 4. **Port design scripts as viewModel hooks.** Custom elements and WebGL/canvas widgets become hooks on an empty React host (`dangerouslySetInnerHTML={{__html:""}}`) — never append into React-owned nodes (see [seo-aeo-geo-prerender.md](../features/seo-aeo-geo-prerender.md)). Engines skip under `isPrerender()` and pause off-screen and on a hidden tab. Keep the design's timing backstops (IntersectionObserver + rect polls, `setTimeout` retry chains) — rAF / ResizeObserver can go silent on cold load.
42
+ 4. **Port design scripts as viewModel hooks** — except a self-contained script that only draws (no React-owned DOM): ship it unchanged (`src/pages/<Page>/vendor/`, or `public/assets/<page>/` loaded by a script tag), e.g. blog `toga-thumbs.js` and `toga-intro/`. Custom elements and WebGL/canvas widgets become hooks on an empty React host (`dangerouslySetInnerHTML={{__html:""}}`) — never append into React-owned nodes (see [seo-aeo-geo-prerender.md](../features/seo-aeo-geo-prerender.md)). Engines skip under `isPrerender()` and pause off-screen and on a hidden tab. Keep the design's timing backstops (IntersectionObserver + rect polls, `setTimeout` retry chains) — rAF / ResizeObserver can go silent on cold load.
43
43
  5. **Reuse the shared page pieces — do not re-port them:** `components/FooterV2`, `components/TalosLauncher` (orb + guide bubbles; props `fields`, `liftObstacles`, optional `leftHero` / `waitForPins`; guide memory is per page, keyed by section ids; greeting optional; orb/guide CSS comes from the page's own generated CSS), `components/SiteFaq` (+ `useFaqAccordion`), `components/Reveal`, `components/TalosMark`, `utils/hooks/useBtnSpot`. Add the route to AppLayout `ROUTES_WITH_OWN_FOOTER` / `ROUTES_WITH_OWN_LAUNCHER`. Give every pinned scene `data-nav-pinned` ([navv2-global-nav.md](../features/navv2-global-nav.md)).
44
44
  6. **Wire the route** as a code-split prerendered route — checklist in [seo-aeo-geo-prerender.md](../features/seo-aeo-geo-prerender.md).
45
45
  7. **Map design links** (`*.html`) to real routes; an anchor the target page lacks falls back to the route (NavV2 Gotchas).
@@ -54,4 +54,5 @@ The designer ships static HTML + in-browser JSX + CSS prototypes. Each redesigne
54
54
  - Tailwind preflight is global and differs from browser defaults — check margins, lists and button resets when a section looks off.
55
55
 
56
56
  ## Change history
57
+ - 2026-10-20 — /blog port: `design-blog/` drop + its own port tools; ship self-contained draw scripts unchanged (TRUE-82653, tcox)
57
58
  - 2026-10-15 — Created from the /talos and /our-platform ports: drop + tool locations, EDITS asserts, page root as `<html>`, shared pieces, 12px screenshot offset (TRUE-82652, tcox)
@@ -6,7 +6,7 @@ project: TOGA Technology Website
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-15
9
+ updated: 2026-10-20
10
10
  owners: ["tcox"]
11
11
  files:
12
12
  - togatech/package.json
@@ -20,11 +20,11 @@ related:
20
20
  - designer-prototype-port.md
21
21
  ---
22
22
 
23
- Get togatech running on a dev machine and verify a data-driven section without Cypress — `npm install` first, `lint` is dead, Cypress 15 fails on Windows. Open before local verification.
23
+ Get togatech running on a dev machine and verify a page — `npm install` first, `lint` is dead, unset `ELECTRON_RUN_AS_NODE` for Cypress, Puppeteer fallback. Open before local verification.
24
24
 
25
25
  ## Summary
26
26
 
27
- The gotcha-avoidance path for getting `togatech` up on a dev machine and actually *seeing* a data-driven section render. Three things bite in order: **stale `node_modules`** (the dev server fails outright), **`lint` cannot run from a clean install**, and **Cypress 15's binary fails its smoke test on Windows** — plus a persisted React Query cache that makes it look like nothing is fetching. Verified on Windows 11, 2026-08-13.
27
+ The gotcha-avoidance path for getting `togatech` up on a dev machine and actually *seeing* a data-driven section render. Three things bite in order: **stale `node_modules`** (the dev server fails outright), **`lint` cannot run from a clean install**, and **Cypress fails its smoke test unless `ELECTRON_RUN_AS_NODE` is unset** — plus a persisted React Query cache that makes it look like nothing is fetching. Verified on Windows 11, 2026-08-13.
28
28
 
29
29
  ## Steps
30
30
 
@@ -32,7 +32,7 @@ The gotcha-avoidance path for getting `togatech` up on a dev machine and actuall
32
32
  2. **Start the dev server** — `npm run dev`, or `npm run togatech` for the `--host togatech` variant (Vite `allowedHosts: ["togatech"]`).
33
33
  3. **Clear localStorage before judging any data fetch.** `App.tsx` wraps the app in `PersistQueryClientProvider` with the persister key **`"commerce"`** and a **24h `staleTime`**, so a previously cached query (e.g. `jobPosts`) renders from localStorage with **no network request at all**. Clear the persisted cache **and** `accessToken` / `refreshToken` — a stale public token from `POST /v2/auth/public` will otherwise be reused too.
34
34
  4. **Do not expect `npm run lint` to work.** The script is `eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0`, but **`eslint` is not in `package.json` dependencies or devDependencies at all** — there is no local binary, so a clean install cannot lint. There is also **no ESLint config** — ad hoc `npx eslint` fails with "couldn't find an eslint.config.* file". Type-check with `npx tsc --noEmit -p tsconfig.app.json` — **not** `-p .`: root `tsconfig.json` has `"files": []` (references only), so `tsc -p .` and the `tsc` step in `npm run build` check nothing. It has pre-existing errors (13 on 2026-10-12, in Modal.tsx, PinScroll.tsx, TalosChat.tsx, IQ.tsx, OurHistory.tsx, routes.tsx, getFontAwesomeIcon.tsx), so filter the output by the paths you touched.
35
- 5. **If Cypress won't run, verify headlessly with the repo's Puppeteer.** `cypress` 15.11 fails its own binary verification on this Windows machine — `bad option: --smoke-test` — and `npx cypress install` does **not** fix it. Workaround: `puppeteer` is already a dependency, so drive **system Chrome** directly by passing `executablePath` to `puppeteer.launch()`, navigate to the dev-server URL, and assert on the rendered DOM (the section testids, e.g. `job-posting-section` / `job-opening-cards`). Same technique the production build already uses in `scripts/prerender.mjs`.
35
+ 5. **Run Cypress from Claude's shell as `env -u ELECTRON_RUN_AS_NODE npx cypress run --spec <spec>`.** Claude's shell sets `ELECTRON_RUN_AS_NODE`, so Cypress's Electron binary fails its smoke test (`bad option: --smoke-test`); `npx cypress install` does **not** fix it. Verified working 2026-10-20 (blogPage spec). **Fallback — verify headlessly with the repo's Puppeteer:** `puppeteer` is already a dependency, so drive **system Chrome** directly by passing `executablePath` to `puppeteer.launch()`, navigate to the dev-server URL, and assert on the rendered DOM (the section testids, e.g. `job-posting-section` / `job-opening-cards`). Same technique the production build already uses in `scripts/prerender.mjs`.
36
36
 
37
37
  ## Gotchas
38
38
 
@@ -51,6 +51,7 @@ The gotcha-avoidance path for getting `togatech` up on a dev machine and actuall
51
51
  - [Careers Section — Live Paylocity Job Openings](../features/careers-paylocity-job-openings.md)
52
52
 
53
53
  ## Change history
54
+ - 2026-10-20 — Cypress `--smoke-test` failure = `ELECTRON_RUN_AS_NODE` in Claude's shell; unset it to run specs (TRUE-82653, tcox)
54
55
  - 2026-10-15 — Git Bash route-arg path conversion (`MSYS_NO_PATHCONV=1`); link to design-compare gotchas (TRUE-82652, tcox)
55
56
  - 2026-10-12 — Root tsconfig checks nothing (build's `tsc` does not type-check); 13 known errors (tcox)
56
57
  - 2026-10-05 — Prod build sends GA hits from localhost; robotsPlugin closeBundle fails without dist/ (tcox)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1101",
3
+ "version": "1.0.1102",
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",