toga-ai 1.0.642 → 1.0.644
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/library/INDEX.md +1 -1
- package/knowledge/1.0/apps/library/features/netsuite-suiteql-rest-shim.md +39 -2
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/netsuite-rest-client.md +48 -1
- package/knowledge/sessions/2026-08-25-bdr-landing-chat-tcox.md +84 -0
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
|
|
17
17
|
| [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
|
|
18
18
|
| [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
|
|
19
|
-
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php |
|
|
19
|
+
| [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php, library/app/netsuite.php |
|
|
20
20
|
| [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc | library/app/systemmonitor/netsuiteintegration.php, worker/crons/infrastructure/system_monitors.php |
|
|
21
21
|
| [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. | library/app/api/toga2.php, library/app/api/startechticket.php, worker/crons/toga2/startech/common_import_supporting_records.php |
|
|
22
22
|
| [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross | library/app/api/toga2.php, worker/crons/toga2/aig/sync_togasupply_aig.php, worker/crons/toga2/wje/sync_togasupply_wje.php, test/@Mark/AIG/test_multi_email.php |
|
|
@@ -6,14 +6,16 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [dfranks]
|
|
9
|
+
updated: 2026-08-25
|
|
10
|
+
owners: [dfranks, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
|
+
- library/app/netsuite.php
|
|
13
14
|
related:
|
|
14
15
|
- netsuite-suiteql-api-reference.md
|
|
15
16
|
- ../architecture.md
|
|
16
17
|
- ../../worker/features/forecast2-netsuite-reconciliation.md
|
|
18
|
+
- ../../../2.0/apps/_underscore/features/netsuite-rest-client.md
|
|
17
19
|
---
|
|
18
20
|
|
|
19
21
|
## Summary
|
|
@@ -90,6 +92,35 @@ Forecast2 tables already store.
|
|
|
90
92
|
- **TO_DATE rejects impossible dates** (e.g. `2026-06-31`) with an opaque HTTP 400 — validate
|
|
91
93
|
calendar dates before building SuiteQL.
|
|
92
94
|
|
|
95
|
+
## Transaction status sourcing (1.0) — and the 2026.2 REST `status.id` change
|
|
96
|
+
|
|
97
|
+
1.0 reaches NetSuite over **two transports, and neither reads status off a REST record GET
|
|
98
|
+
body**:
|
|
99
|
+
|
|
100
|
+
- **REST adapter (`App_Api_Netsuite_Rest`, `rest.php`).** It *does* issue `/record/v1/...`
|
|
101
|
+
GETs, but it sources `status` from the **SuiteQL header row** (`$h->status`, from the
|
|
102
|
+
`SELECT ... status ... FROM ...` at ~L381/574/754 — already a letter) and maps that letter
|
|
103
|
+
to the SOAP-era text label via an internal **`$statusMap`** (~L471-472, L625-626,
|
|
104
|
+
L853-854/863). The REST GET body (`$rec`) is only read for shipMethod / opportunity / terms /
|
|
105
|
+
createdFrom / country — **never status**. Downstream forecast2 crons
|
|
106
|
+
(`worker/crons/toga2/forecast2/*`) compare `$nsRecord->status` to text, but that text is
|
|
107
|
+
*adapter-produced* (from the SuiteQL letter via `$statusMap`), not from the changing REST
|
|
108
|
+
field.
|
|
109
|
+
- **SOAP (`App_NetSuite`, `library/app/netsuite.php`).** Always uses `new NetSuiteService()`
|
|
110
|
+
(SuiteTalk toolkit) and reads transaction status off the SOAP record as text (e.g.
|
|
111
|
+
`$items->status != 'Billed'/'Cancelled'/'Closed'` ~L1151/L1434; `$nsInvoice->status ==
|
|
112
|
+
'Paid In Full'` ~L2546). SOAP is a different API and is unaffected. (The many
|
|
113
|
+
`->status->isSuccess` / `statusDetail` reads elsewhere are the SOAP **response envelope**,
|
|
114
|
+
not the record's business status.)
|
|
115
|
+
|
|
116
|
+
> **NetSuite 2026.2 impact: none for 1.0.** 2026.2 standardizes the `status.id` returned by
|
|
117
|
+
> **REST record GETs** for transactions to single-letter codes across all types (some types
|
|
118
|
+
> return it as text like `"Open"` today); `status.refName` and SuiteQL are unchanged, and
|
|
119
|
+
> SOAP is a separate API. Since no 1.0 code reads `status.id` off a REST GET body, no patch is
|
|
120
|
+
> needed. NetSuite SuiteAnswers ID **89313**. The 2.0 side of the same conclusion (shared REST
|
|
121
|
+
> client + worker2 consumers) is in
|
|
122
|
+
> [`netsuite-rest-client.md`](../../../2.0/apps/_underscore/features/netsuite-rest-client.md).
|
|
123
|
+
|
|
93
124
|
## Data model
|
|
94
125
|
|
|
95
126
|
Reads NetSuite `transaction`, `transactionline`, `previoustransactionlinelink` via SuiteQL and
|
|
@@ -108,6 +139,12 @@ None — uniform across clients (NetSuite is a single shared account).
|
|
|
108
139
|
|
|
109
140
|
## Change history
|
|
110
141
|
|
|
142
|
+
- 2026-08-25 — Documented **1.0 transaction-status sourcing across both transports** and the
|
|
143
|
+
conclusion that the **NetSuite 2026.2 REST `status.id` text→letter standardization does not
|
|
144
|
+
affect 1.0** (SuiteAnswers 89313). `App_Api_Netsuite_Rest` derives status from the SuiteQL
|
|
145
|
+
letter and maps it via an internal `$statusMap` — never from the REST GET body; forecast2
|
|
146
|
+
crons see that adapter-produced text, not the changing field; `library/app/netsuite.php` is
|
|
147
|
+
pure SOAP (a different API). Discovery/impact-analysis only — no code changed. (jcardinal)
|
|
111
148
|
- 2026-06-11 — Documented bulk SuiteQL field semantics (tl.id==line, ShipItem NULL cost, iscogs
|
|
112
149
|
COGS filter, createdFrom non-determinism, foreignamountunpaid, sign conventions) surfaced while
|
|
113
150
|
moving the Forecast2 trueup tools off per-id REST GETs. (dfranks)
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
| [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php, _underscore/Model.php, _underscore/Model/Rate/Subscription.php |
|
|
31
31
|
| [_Model::save() parent FK cascade — stored-SQL-field recompute deadlocks](features/model-save-parent-cascade-stored-field-deadlock.md) | `_Model::save()` runs a **generic parent foreign-key cascade**: inserting (or saving) a child row that carries an FK to a parent causes `_Model` to **re-load an | _underscore/Model.php, _underscore/Model/Client/PurchaseOrder.php, _underscore/Model/Client/AdvanceShippingNotice.php |
|
|
32
32
|
| [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` is a plain load-then-write ORM primitive and **cannot express an atomic conditional update** (an optimistic-concurrency / row-claim guard such | _underscore/Model.php, _underscore/Query.php, _underscore/Model/Rate/Subscription.php |
|
|
33
|
-
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
33
|
+
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php, _underscore/Trait/Netsuite/SalesOrder.php, worker2/Worker/Netsuite/SalesOrder.php, worker2/Component/Forecast/SaleImport/SaleImport.php, worker2/Worker/Netsuite/Opportunity.php |
|
|
34
34
|
| [NetSuite Sales Order sync — ship-to address, phone, and PO reference sourcing](features/netsuite-salesorder-address-phone-sync.md) | `_Trait_Netsuite_SalesOrder` is the **shared** sales-order importer composed into **22 client models** (every client on the dbchanges2 `netsuite` module). | _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Model.php, dbchanges2/_modules/netsuite/2026-08-10a - AddressPhoneNumberApiRoleAcl.sql |
|
|
35
35
|
| [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | `_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>` — still the live path for `toga2-supply` and other pre-Surf | _underscore/Model/Core/Page.php, _underscore/Model/Client/TableView.php, toga2-supply/src/components/ui/Tables/PrimaryTable/PrimaryTable.tsx |
|
|
36
36
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/Model.php, _underscore/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
|
|
@@ -6,14 +6,19 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-25
|
|
10
10
|
owners: ["dfranks", "jcardinal", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Component/Api/Netsuite/Netsuite.php
|
|
13
|
+
- _underscore/Trait/Netsuite/SalesOrder.php
|
|
14
|
+
- worker2/Worker/Netsuite/SalesOrder.php
|
|
15
|
+
- worker2/Component/Forecast/SaleImport/SaleImport.php
|
|
16
|
+
- worker2/Worker/Netsuite/Opportunity.php
|
|
13
17
|
related:
|
|
14
18
|
- ../../worker2/features/netsuite-salesorder-open-orders-sync.md
|
|
15
19
|
- ../../worker2/features/netsuite-salesorder-outbound-push.md
|
|
16
20
|
- ../../worker2/features/netsuite-opportunity-sync.md
|
|
21
|
+
- ../../library/features/netsuite-suiteql-rest-shim.md
|
|
17
22
|
- ../architecture.md
|
|
18
23
|
---
|
|
19
24
|
|
|
@@ -167,6 +172,40 @@ rather than a bad query. Confirmed live 2026-07-28.
|
|
|
167
172
|
Surfaced building the NYCHH asset-tag backfill (see
|
|
168
173
|
[nychh-asset-tag-backfill](../../worker2/features/nychh-asset-tag-backfill.md)).
|
|
169
174
|
|
|
175
|
+
## NetSuite transaction status sourcing (2.0) — and the 2026.2 REST `status.id` change
|
|
176
|
+
|
|
177
|
+
**`fetchRecord()` returns the REST record UNTOUCHED — there is no central status
|
|
178
|
+
normalization in the shared client.** `fetchRecord()` (`Netsuite.php` ~L215-223) does
|
|
179
|
+
`send('GET', route)`, json-decodes, and hands the raw record back; it never reads or maps
|
|
180
|
+
`status`. So whether a 2.0 consumer is exposed to a change in the REST record's `status`
|
|
181
|
+
field depends entirely on *that consumer*, not on the client. Audited 2026-08-25 — every 2.0
|
|
182
|
+
consumer sources transaction status from a change-safe field:
|
|
183
|
+
|
|
184
|
+
- **`worker2/Worker/Netsuite/SalesOrder.php`** reads `status->refName` (the human-readable
|
|
185
|
+
label) and compares it to `OPEN_STATUSES` text — never `status.id`.
|
|
186
|
+
- **`worker2/Component/Forecast/SaleImport/SaleImport.php`** — `statusName()` (~L845-853) reads
|
|
187
|
+
`status->refName`, matched against `EXCLUDED_STATUSES` text. This is the shared engine that
|
|
188
|
+
Invoice / CashSale / CreditMemo / CashRefund import all delegate to.
|
|
189
|
+
- **`worker2/Worker/Netsuite/Opportunity.php`** — `fetchStatus()` (~L431-444) gets transaction
|
|
190
|
+
status via **SuiteQL** (`SELECT status FROM transaction`), which already returns the letter
|
|
191
|
+
and maps `A`/`C`/`D`. (Its `entityStatus.id` at ~L197 is a *different* field — an opaque
|
|
192
|
+
lookup key, not the transaction status.)
|
|
193
|
+
- **`_underscore/Trait/Netsuite/SalesOrder.php`** — the one text `switch ($nsOrder->status)`
|
|
194
|
+
(~L589-620) is driven by a **SOAP** `$service->get()` record, not REST.
|
|
195
|
+
- **`api2`** makes no NetSuite calls of its own — it only registers the SDK at boot and writes
|
|
196
|
+
one hardcoded outbound status literal (`PENDING_FULFILLMENT`); its `->status` reads are its
|
|
197
|
+
own HTTP response envelope. Not a NetSuite status consumer.
|
|
198
|
+
|
|
199
|
+
> **NetSuite 2026.2 impact: none for 2.0.** NetSuite 2026.2 standardizes the `status.id` field
|
|
200
|
+
> returned by **REST record GETs** for transaction records to single-letter codes (A/B/D/E/V)
|
|
201
|
+
> across all transaction types (today some types return `status.id` as text like `"Open"`);
|
|
202
|
+
> `status.refName` and SuiteQL are unchanged, and SOAP/SuiteTalk is a different API. Because no
|
|
203
|
+
> 2.0 code reads `status.id` off a REST GET body as text, no patch is needed. NetSuite
|
|
204
|
+
> SuiteAnswers ID **89313**. (Not audited: other 2.0 apps that may consume this client —
|
|
205
|
+
> `toga2-supply`, `toga25-supply`, `tools` — check them the same way if touched.) The 1.0 side
|
|
206
|
+
> of the same conclusion is in
|
|
207
|
+
> [`netsuite-suiteql-rest-shim.md`](../../library/features/netsuite-suiteql-rest-shim.md).
|
|
208
|
+
|
|
170
209
|
## Logging
|
|
171
210
|
|
|
172
211
|
`send()` calls `setLogging(false)`, so **NetSuite REST request/response bodies are NOT written to
|
|
@@ -192,6 +231,14 @@ doc.)
|
|
|
192
231
|
|
|
193
232
|
## Change history
|
|
194
233
|
|
|
234
|
+
- 2026-08-25 — Documented **2.0 NetSuite transaction-status sourcing** and the conclusion that
|
|
235
|
+
the **NetSuite 2026.2 REST `status.id` text→letter standardization does not affect 2.0**
|
|
236
|
+
(SuiteAnswers 89313). `fetchRecord()` returns the REST record raw (no central status
|
|
237
|
+
normalization), and every audited consumer reads status from `status.refName` or SuiteQL, or
|
|
238
|
+
over SOAP — none reads REST `status.id` as text: worker2 SalesOrder/SaleImport (`refName`),
|
|
239
|
+
worker2 Opportunity (`fetchStatus()` via SuiteQL), `_underscore` SalesOrder trait (SOAP),
|
|
240
|
+
api2 (no NetSuite calls). Discovery/impact-analysis only — no code changed. (jcardinal)
|
|
241
|
+
|
|
195
242
|
- 2026-08-20 — Added **idempotent creates via `externalId`**: `createRecord()` has no idempotency, so
|
|
196
243
|
stamp an `externalId` (NetSuite rejects a duplicate within the account) and read the record back
|
|
197
244
|
with `GET /record/v1/<record>/eid:<externalId>` — the non-SuiteQL fallback, which matters because
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: bdr-landing-chat
|
|
4
|
+
title: BDR /landing mobile pass + Talos chat drawer wired to real campaign-chat backend
|
|
5
|
+
author: tcox
|
|
6
|
+
repos: [bdr]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-08-25
|
|
11
|
+
updated: 2026-08-25
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: bdr-landing-chat
|
|
15
|
+
**Date:** 2026-08-25
|
|
16
|
+
**Project/Repo:** bdr (2.0)
|
|
17
|
+
**Task:** On the BDR cybersecurity landing page (C:\WWW\BDR, route `/landing`): did the pending mobile CSS pass, then built the right-side Talos chat drawer (opened by the header "Ask Alex Anything" CTA and the hero "Learn more" button) wired to the REAL api.togaiq.com campaign-chat backend behind a blox-shaped adapter, captured everything to the KB, and retired the now-deleted `AI BDR - Security/` mockup from docs + eslint config. Nothing is committed in the BDR repo.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- **Mobile pass** appended to `src/app/security.css`: `@media(max-width:560px)` (hero title `clamp(29px,8.4vw,35px)`, hero CTAs stack full-width at 54px, `.lp-value` padding 20px, `.lp-value-motes` inset `-30px -10px` to stop the mote spill opening horizontal scroll at phone width, card/stat padding trims, section spacing) and `@media(max-width:420px)` (`.wordmark-tech` hidden, `.lp-stats` gap 12px). All scoped under `.security-shell`. Evidence: dev server compiled clean, `next build` clean, `/landing` still prerendered static.
|
|
23
|
+
- **Chat drawer built and wired.** New `src/components/ChatPanel.tsx` (drawer shell ported from the mockup's `ChatPanel`), `src/lib/talosChat.ts` (adapter: 64-hex fingerprint in `localStorage["talos_fp"]`, 15-min session token from `POST /api/ai/talos-session` re-minted once on 401, `POST /api/ai/talos-chat` with `campaign_id 43fdf2b6-7a5d-4e85-be6b-911f42aa787f`, message-shape enforcement in `buildApiMessages()`, scripted fallback when unreachable), `src/components/ChatMarkdown.tsx` (safe bold+bullets renderer, text nodes only), `.chatveil/.chatpanel` CSS block in `security.css`, `talos` content group in `src/content/security.ts`, `chatOpen` state + `<ChatPanel>` mount in `src/flow/screens/SecurityLanding.tsx`. Evidence: `npx tsc --noEmit` clean, `npm run lint` clean (incl. no-em-dash rule), `npx vitest run` **367/367** (30 new tests in `test/talosChat.test.ts` + `test/chatMarkdown.test.ts`), `npx next build` clean, `curl` `/` -> 307 -> `/landing` -> 200.
|
|
24
|
+
- **KB captured and pushed** (team repo `_main`): `campaign-chat-handoff.md` created (f27d7f5, API key VALUE deliberately omitted); `/capture` wrote `landing-chat-drawer.md` (new) + updated `security-landing-page.md`, `campaign-chat-handoff.md`, `toga-blox/features/talos-assistant.md` (bfd7ea8); mockup-deletion follow-up pushed (595620a).
|
|
25
|
+
- **Dead eslint ignore removed**: `"AI BDR - Security/**"` dropped from `eslint.config.mjs` `globalIgnores` after the developer deleted that mockup folder; lint still clean (the 96 errors it used to hide are gone with the folder).
|
|
26
|
+
- **Decision path confirmed with the developer via AskUserQuestion**: "Bridge now, blox later" + "Real backend now" (both recommended options accepted).
|
|
27
|
+
|
|
28
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
29
|
+
- **Spawning the `context-primer` subagent at kickoff Step 4**: hard-blocked by the `kickoff-gate.js` hook (stage 2) with "prime it YOURSELF, IN THE MAIN SESSION — do NOT spawn a subagent". The kickoff skill text says to spawn it; the hook wins. Correct flow: Read the preflight `reads[]` docs directly in the main thread, then run `node "<TEAM_REPO>/knowledge.js" kickoff-primed` to release the gate.
|
|
30
|
+
- **`git push origin _main` in the team knowledge repo without rebasing first**: rejected non-fast-forward TWICE this session (CI "chore: bump version" commits land constantly). Always `git pull --rebase origin _main` immediately before the push, as a separate command; a one-shot `push || (pull --rebase && push)` bash chain did not run the fallback as expected.
|
|
31
|
+
- **Writing a long markdown file via a Bash single-quoted heredoc**: failed with "unexpected EOF while looking for matching `''" (the body's backticks/quotes tripped the shell). Use the Write tool for multi-line files.
|
|
32
|
+
- **Glob on `c:\WWW` or the BDR repo root** (carried forward): ripgrep 20s timeout on the huge tree. Use Bash `ls`/`find` with explicit paths.
|
|
33
|
+
- **Grep content-mode showing `\*` for CSS `/*` comment openers** (carried forward): display artifact, NOT the mockup's literal-`\n` corruption. Do not "fix" those lines.
|
|
34
|
+
- **Consuming blox Talos directly ("force blox now")**: rejected before trying, for the record — blox Talos is unpublished to npm (supply uses a local symlink) and blox peers cap React at `^18` while BDR is React `19.2.4`; would also drag react-router-dom/FA-Pro registry auth into a zero-dep Next app. Do not attempt until blox publishes with React 19 peers.
|
|
35
|
+
|
|
36
|
+
## Not tried yet (candidates for next session)
|
|
37
|
+
- **Confirm the backend is actually reachable from a browser**: api.togaiq.com endpoints are origin/UA-gated; `localhost:3000`, `bdr.dev.sandbox.togatech.com`, and the prod domain must be allowlisted by the Talos backend owner. Live replies carry suggestion chips; scripted-fallback replies carry none — that is the tell.
|
|
38
|
+
- Render the reply's `cta` button (`book_call`/`contact`/`learn_more`) and `cited_slides` thumbnails (needs `GET /api/ai/campaign/{id}`; presigned image URLs expire ~7 days, re-fetch on load/error). Both are typed in `TalosResponse` but unrendered.
|
|
39
|
+
- Visual QA of the drawer at phone widths (`width:min(400px,92vw)`) — only desktop was smoke-tested via curl/build; no screenshot pass done.
|
|
40
|
+
- Verify the Calendly slug `toga-technology/risk-assessment` is live (still a mockup placeholder).
|
|
41
|
+
- Graft `src/content/security.ts` into the `CampaignBundle` schema when the landing becomes campaign-driven.
|
|
42
|
+
- Contribute `talosChat.ts` (+ `ChatMarkdown.tsx`) upstream to blox for TRUE-80692's deferred "real onSend backend adapter" ticket — tell apeterson; make `baseUrl` a required option there (blox consumers are Vite, `process.env.NEXT_PUBLIC_TALOS_API_BASE` is a Next-ism).
|
|
43
|
+
- **Commit**: everything is working-tree only. Branch creation needs tcox's go-ahead (team rule). `git status` shows the deleted mockup files as `AD` (staged-added, then deleted) — `git add -A` resolves that.
|
|
44
|
+
- Backend owner should rotate the exposed `campaign:manage` key (key_id `2c2a4389-272f-4f05-b36f-f9b6f3de033f`) before go-live — value lives in the secret manager / source handoff, never in the KB.
|
|
45
|
+
- KB hygiene (unrelated to BDR): 20+ knowledge docs end with stray `</content>` / `</invoke>` lines (`grep -rln "^</invoke>$" knowledge/`); `validate` does not catch it. Needs a dedicated cleanup pass.
|
|
46
|
+
|
|
47
|
+
## Current file state
|
|
48
|
+
| File | Status | Notes |
|
|
49
|
+
|------|--------|-------|
|
|
50
|
+
| src/app/security.css | modified | + mobile pass (560px/420px blocks) + `.chatveil/.chatpanel` drawer block + `.chatmd-*` rules, all under `.security-shell` |
|
|
51
|
+
| src/components/ChatPanel.tsx | created | Right-side drawer shell; Escape/scrim close, focus in/restore; one standing action "Book a meeting" -> Calendly; deliberately disposable (blox TalosPanel later) |
|
|
52
|
+
| src/lib/talosChat.ts | created | `createTalosChatAdapter()` -> `TalosAdapter { onSend(text), reset() }`; fingerprint, token mint/re-mint-once, `buildApiMessages()`, `toTalosResponse()`, `scriptedReply()` fallback; `fetchImpl` test seam |
|
|
53
|
+
| src/components/ChatMarkdown.tsx | created | `toBlocks()` + `renderBoldSpans()`; p/ul/li/strong only, no HTML/links |
|
|
54
|
+
| src/content/security.ts | modified | + `talos` group (title, greeting, chips, placeholder, bookCta, disclaimer, campaignId, fallback entries); dev re-indented to tabs and renamed header CTA to "Ask Alex Anything" |
|
|
55
|
+
| src/flow/screens/SecurityLanding.tsx | modified | `chatOpen` state; header CTA + "Learn more" `onClick` open drawer; `<ChatPanel>` mounted inside `.security-shell`; header comment updated |
|
|
56
|
+
| eslint.config.mjs | modified | removed dead `"AI BDR - Security/**"` ignore (`mockup/**` kept) |
|
|
57
|
+
| test/talosChat.test.ts | created | 23 tests: fingerprint, message shaping (incl. 30-cap alternation), scripted matcher, response filtering, adapter mint/cache/401-re-mint/fallback/reset |
|
|
58
|
+
| test/chatMarkdown.test.ts | created | 7 tests: blocks, CRLF, bullets, html-looking text stays literal, bold spans, unclosed `**` |
|
|
59
|
+
| AI BDR - Security/ (folder) | deleted by dev | mockup prototype removed from repo after port completed; shows as `AD` in git status |
|
|
60
|
+
| mockup/ (studio flow prototype) | untouched | still in repo |
|
|
61
|
+
| src/proxy.ts | untouched | matcher still only `/` + 5 API routes; drawer talks to a third-party origin, limiter not applicable |
|
|
62
|
+
|
|
63
|
+
All changes are **uncommitted, working tree only** — awaiting tcox's branch/commit go-ahead.
|
|
64
|
+
|
|
65
|
+
## Decisions made
|
|
66
|
+
- **"Bridge now, blox later" (TRUE-80692)** — build the drawer shell now with the adapter deliberately mirroring blox's `TalosAdapter` seam (`onSend(text) => Promise<TalosResponse>`), so the later swap to blox `TalosPanel` is a re-wire. Rejected: waiting for blox (CTAs would stay inert indefinitely); forcing blox in (unpublished + React 18 peer cap + heavy peer deps).
|
|
67
|
+
- **Wire the real backend now, scripted answers as fallback only** — the campaign-chat contract was already in the KB; the mockup's `askAlex` matcher became data in `security.ts` `talos.fallback`. Rejected: shipping stub-only.
|
|
68
|
+
- **Hand-rolled `ChatMarkdown` instead of `@assistant-ui/react-markdown` / react-markdown** — zero new deps, emits text nodes only, trivially satisfies the handoff's no-HTML/no-unsafe-link rule. Markdown fails soft so unsupported constructs render as text.
|
|
69
|
+
- **"Call me now" NOT rendered in the drawer** — its mockup destination is the parked studio call screen. Standing action is "Book a meeting" -> the page's Calendly href. `cta`/`cited_slides` deferred.
|
|
70
|
+
- **Never read the backend's silent lead/qualification fields** — only `message/suggestions/cta/cited_slides` cross into `TalosResponse` (per the handoff; no CRM sink exists yet).
|
|
71
|
+
- **`campaignId` lives in content (`security.ts`), base URL has an env override** (`NEXT_PUBLIC_TALOS_API_BASE`, default `https://api.togaiq.com`). Campaign id is not a secret; per-landing-page config belongs with the page's content.
|
|
72
|
+
- **Browser-direct calls to api.togaiq.com, not a Next route proxy** — the session token is bound to client IP + UA; proxying server-side would break that binding. The flow is keyless by design so no secret crosses to the client.
|
|
73
|
+
- **API key VALUE kept out of the KB** — the source handoff embeds the `campaign:manage` key in plaintext; the knowledge repo is on GitHub and team rules forbid committing secrets. Recorded key_id + location + rotate-before-go-live only.
|
|
74
|
+
- **KB filing stays under `2.0/apps/ai-bdr/`** — no `2.0/apps/bdr/` tree; that split remains a deliberate future elevated decision.
|
|
75
|
+
- **Mockup deletion (dev's call) reflected in docs, not erased** — provenance kept as history; live-path references, the porting gotchas, and the eslint ignore retired.
|
|
76
|
+
|
|
77
|
+
## Blockers
|
|
78
|
+
None hard. Pending external inputs: (1) Talos backend owner to allowlist BDR origins on api.togaiq.com (until then the drawer silently uses scripted fallback); (2) design decisions on rendering `cta`/`cited_slides` and on the Calendly link; (3) tcox's go-ahead to branch/commit.
|
|
79
|
+
|
|
80
|
+
## Exact next step
|
|
81
|
+
> In `C:\WWW\BDR` run `npm run dev`, open http://localhost:3000/landing, click "Ask Alex Anything", and send "What does the free assessment scan?". If the reply shows suggestion chips, the backend is live from localhost; if it shows none (scripted fallback), message the Talos backend owner to allowlist `localhost:3000` and `bdr.dev.sandbox.togatech.com` on api.togaiq.com. Then get tcox's branch go-ahead and commit the working tree (`git add -A` also resolves the `AD` mockup deletions).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
_Saved by /session-save on 2026-08-25_
|
package/package.json
CHANGED