toga-ai 1.0.209 → 1.0.211
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/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +63 -1
- package/knowledge/2.0/apps/_underscore/features/netsuite-rest-client.md +5 -3
- package/knowledge/INDEX.md +1 -1
- package/knowledge/standalone/apps/togatech/INDEX.md +2 -1
- package/knowledge/standalone/apps/togatech/features/seo-aeo-geo-prerender.md +21 -2
- package/knowledge/standalone/apps/togatech/workflows/creating-pull-requests.md +47 -0
- package/knowledge/standalone/standards/brand-naming.md +35 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql |
|
|
7
7
|
| [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
|
|
8
8
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
9
|
-
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php |
|
|
9
|
+
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, test/@dave/test_invoice_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
10
10
|
| [_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 |
|
|
11
11
|
| [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 |
|
|
12
12
|
| [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/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: [dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Component/Forecast/SaleImport/SaleImport.php
|
|
@@ -17,9 +17,14 @@ files:
|
|
|
17
17
|
- worker2/Worker/Netsuite/CashRefund.php
|
|
18
18
|
- worker2/Worker/Netsuite/Opportunity.php
|
|
19
19
|
- worker2/Worker/Netsuite/SalesOrder.php
|
|
20
|
+
- test/@dave/test_invoice_lifecycle.php
|
|
21
|
+
- test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
|
|
22
|
+
- test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js
|
|
20
23
|
related:
|
|
21
24
|
- ../architecture.md
|
|
22
25
|
- ../../worker2/architecture.md
|
|
26
|
+
- ../../worker2/features/netsuite-salesorder-open-orders-sync.md
|
|
27
|
+
- ./netsuite-rest-client.md
|
|
23
28
|
---
|
|
24
29
|
|
|
25
30
|
## Summary
|
|
@@ -144,7 +149,51 @@ events as specific **subtypes** (`inventoryItem`, `nonInventoryResaleItem`, `kit
|
|
|
144
149
|
would be needed only for a future real-time item webhook, **not** for this Sales importer
|
|
145
150
|
(which keeps items fresh via the hourly item pull cron plus the inline self-heal).
|
|
146
151
|
|
|
152
|
+
## Local testing harness (`test/@dave/test_invoice_lifecycle.php`)
|
|
153
|
+
A CLI that boots worker2/_underscore (chdir to `worker2/` then `require index.php`) and drives a
|
|
154
|
+
**full NetSuite invoice lifecycle** through the live `_underscore` REST client
|
|
155
|
+
(`_Component_Api_Netsuite`) to exercise this importer end-to-end against a local Forecast mirror.
|
|
156
|
+
Actions: `check | create | get | update | delete | sync | recent | amq | deployments | locations`.
|
|
157
|
+
|
|
158
|
+
- **Fixture:** customer **58** ("8 Test Company") + item **103741** ("Test Other Charge for Sale",
|
|
159
|
+
maps to local `Forecast.Items` id 25), **rate 0** → a **$0 invoice that still yields one Sales
|
|
160
|
+
row** (the engine does **not** skip a $0 line — revenue `0.00` is written; same as the cron's
|
|
161
|
+
Sales section). Confirms create→insert, edit→update (tracked-column change-detection), and
|
|
162
|
+
delete→`removeAll` all land in local `Forecast.Sales`.
|
|
163
|
+
- It carries its **own** `createInvoiceRecord()` that POSTs via `_ApiRequest` and parses the
|
|
164
|
+
`Location` header with a **correct** regex (delimiter `~`) — a deliberate workaround for the live
|
|
165
|
+
`_Component_Api_Netsuite::createRecord()` `#`-delimiter bug (see the
|
|
166
|
+
[REST client doc](./netsuite-rest-client.md)); the framework was **not** edited this session.
|
|
167
|
+
- `amq` action SuiteQLs the AMQ custom record; `deployments` SuiteQLs `scriptdeployment`+`script`
|
|
168
|
+
to show which enqueuer fires per record type (used to find the dual-deployment trap below).
|
|
169
|
+
|
|
147
170
|
## Gotchas / known issues
|
|
171
|
+
- **Testing locally pollutes PROD unless you UNDEPLOY the prod AMQ enqueuer first.** Each sale
|
|
172
|
+
transaction type has **TWO** NetSuite UE enqueuer deployments: **"AMQ — Enqueuer"**
|
|
173
|
+
(`customscript_ue_amq_enqueue`, **RELEASED** → posts to **prod** `webhook.togahub.com`) and
|
|
174
|
+
**"AMQ development script"** (`customscript2619` → ngrok via `DEV_OVERRIDE`/`debugWrap`). A
|
|
175
|
+
**RELEASED** deployment fires for **all** actors **including the REST/M2M integration user**, so a
|
|
176
|
+
REST-driven test invoice hits **prod too**. Observed first run: test-invoice edits flowed to prod
|
|
177
|
+
and a $0 row was written to **prod** `Forecast.Sales` — by the **legacy 5-min pull cron**, NOT the
|
|
178
|
+
webhook (prod worker2 has no invoice handler deployed yet, so prod `Netsuite/Invoice/*` jobs
|
|
179
|
+
watchdog-timeout at 300s, `isSuccess=0`). **To isolate a local test, undeploy the prod enqueuer**
|
|
180
|
+
for the record types under test (invoice/cashSale/cashRefund/creditMemo/journalEntry); the second
|
|
181
|
+
full create→update→delete run was then cleanly isolated (zero prod `Logs.Webhook`/`Forecast.Sales`
|
|
182
|
+
for the test id). The **dev** enqueuer's `debugWrap` envelope
|
|
183
|
+
`{action,parameters:{payload,headers}}` is required because a local worker2 has no
|
|
184
|
+
`WebhookIngestion` Lambda to wrap the raw body.
|
|
185
|
+
- **No local app logs for debug-path webhooks — verify arrival via the ngrok inspector, and decode
|
|
186
|
+
the body.** The legacy `{action,parameters}` debug path writes **no** `WorkerJobs`/`Logs.Webhook`
|
|
187
|
+
locally. Confirm inbound arrival at the ngrok request inspector
|
|
188
|
+
`http://127.0.0.1:4040/api/requests/http` — but its **raw body is base64**, so decode the body
|
|
189
|
+
(after the `\r\n\r\n`) before grepping for `internalId`/`eventType`. The inspector buffer is
|
|
190
|
+
**ephemeral** (rotates/clears) — watch it live while triggering.
|
|
191
|
+
- **Sales create webhook races NetSuite REST read-after-write lag** — the handler GETs the invoice
|
|
192
|
+
before its line sublist materializes → **0 lines → 0 rows**. Same gotcha as the SalesOrder
|
|
193
|
+
open-orders sync (see that doc's read-after-write-lag gotcha); it self-heals on the next edit
|
|
194
|
+
webhook or the daily discrepancy-fix.
|
|
195
|
+
- **Local _underscore boots on PHP 8.0.30 (xampp8)** — the floor enforced in `_underscore.php` is
|
|
196
|
+
**8.0.0**, not 8.1.
|
|
148
197
|
- **`_Component_*` classes must live under the `_underscore` framework root, never a project
|
|
149
198
|
repo.** The autoloader rejects a `_Component_*`/`_Model_*` loaded from a project path
|
|
150
199
|
(e.g. `./` under worker2) with "namespace … has not been defined" — at **class-load
|
|
@@ -154,6 +203,19 @@ would be needed only for a future real-time item webhook, **not** for this Sales
|
|
|
154
203
|
- The cron's sign handling is not portable here — see Sign convention.
|
|
155
204
|
|
|
156
205
|
## Change history
|
|
206
|
+
- 2026-06-26 — **Verified the full create→update→delete webhook lifecycle end-to-end on a local box**
|
|
207
|
+
(ngrok tunnel → local worker2 → local `Forecast.Sales`), driving a real **$0** NetSuite invoice
|
|
208
|
+
through create→insert, edit→update (tracked-column change-detection), delete→`removeAll`; confirmed
|
|
209
|
+
a $0 line is **not** skipped (one revenue-`0.00` row). Built the reusable harness
|
|
210
|
+
`test/@dave/test_invoice_lifecycle.php` (boots worker2/_underscore, drives the lifecycle via
|
|
211
|
+
`_Component_Api_Netsuite`; `amq`/`deployments` SuiteQL actions). Recorded durable testing gotchas:
|
|
212
|
+
the **dual AMQ enqueuer** RELEASED-fires-for-the-REST-user prod-pollution trap (undeploy the prod
|
|
213
|
+
enqueuer to isolate; prod's $0 row came from the legacy 5-min pull cron, not a prod webhook —
|
|
214
|
+
prod has no invoice handler so its `Netsuite/Invoice/*` jobs watchdog-timeout `isSuccess=0`); no
|
|
215
|
+
local app logs on the debug `{action,parameters}` path (verify via the ngrok inspector at
|
|
216
|
+
`:4040`, body is base64, buffer ephemeral); the Sales create webhook shares the SalesOrder
|
|
217
|
+
read-after-write line-sublist race; and local _underscore's PHP floor is 8.0.0. No production code
|
|
218
|
+
change. (dfranks)
|
|
157
219
|
- 2026-06-25 — **Verified `sync()` end-to-end across all four sale types** (prod NS reads, local Forecast
|
|
158
220
|
writes; 40+ records spanning invoice/cashSale/creditMemo/cashRefund): revenue reconciled **to the penny**
|
|
159
221
|
against an independent SuiteQL `SUM(-foreignamount)` oracle on every record; the uniform sign factor held
|
|
@@ -57,10 +57,12 @@ through `_ApiRequest` directly (mirroring `send()`'s auth/endpoint/header setup)
|
|
|
57
57
|
> reads the class-`#` as a premature closing delimiter and the whole pattern **always throws
|
|
58
58
|
> `preg_match(): Unknown modifier ']'`**. This fires *after* the record is already created, so the
|
|
59
59
|
> create succeeds in NetSuite but `createRecord()` raises and the caller never gets the new id —
|
|
60
|
-
> it breaks the entire outbound create/push path
|
|
60
|
+
> it breaks the entire outbound create/push path **for any record type** (observed on `journalentry`
|
|
61
|
+
> and again on `invoice` create). **Fix direction:** change the delimiter so `#`
|
|
61
62
|
> isn't both delimiter and class member, e.g. `~/(\d+)(?:[?#]|$)~`. Interim workaround used in
|
|
62
|
-
> probes
|
|
63
|
-
>
|
|
63
|
+
> probes and in `test/@dave/test_invoice_lifecycle.php`: parse the new internalId off the `Location`
|
|
64
|
+
> value carried in the thrown exception message (the lifecycle harness ships its own correct
|
|
65
|
+
> `~`-delimited parse to sidestep this without editing the framework). Confirmed live 2026-06-26.
|
|
64
66
|
|
|
65
67
|
### Update — reuse `send('PATCH', $route, $body)`
|
|
66
68
|
|
package/knowledge/INDEX.md
CHANGED
|
@@ -33,7 +33,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
33
33
|
|
|
34
34
|
## standalone framework
|
|
35
35
|
|
|
36
|
-
- **togatech** (TOGA Technology Website) —
|
|
36
|
+
- **togatech** (TOGA Technology Website) — 4 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
|
|
37
37
|
- **forward** (Forwarder) — 4 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
|
|
38
38
|
|
|
39
39
|
## Clients
|
|
@@ -3,4 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [TOGA Technology Website Architecture](architecture.md) | The public-facing TOGA Technology corporate/marketing website. | togatech/src/main.tsx, togatech/src/App.tsx, togatech/src/routes.tsx, togatech/src/lib/api.ts, togatech/src/lib/contentful.ts, togatech/src/themeConfig/ThemeContext.tsx, togatech/vite.config.ts, togatech/package.json |
|
|
6
|
-
| [SEO / AEO / GEO — Prerendering, Single-Source Meta & Structured Data](features/seo-aeo-geo-prerender.md) | Makes togatech.com visible and citable to search engines **and** AI answer engines (ChatGPT/Perplexity/Claude search, Google AI Overviews). | togatech/vite.config.ts, togatech/scripts/prerender.mjs, togatech/src/routes.config.json, togatech/src/main.tsx, togatech/src/App.tsx, togatech/src/components/SEO/Seo.tsx, togatech/src/components/SEO/JsonLd.tsx, togatech/src/components/SEO/schema.ts, togatech/public/robots.prod.txt, togatech/public/sitemap.xml, togatech/public/llms.txt |
|
|
6
|
+
| [SEO / AEO / GEO — Prerendering, Single-Source Meta & Structured Data](features/seo-aeo-geo-prerender.md) | Makes togatech.com visible and citable to search engines **and** AI answer engines (ChatGPT/Perplexity/Claude search, Google AI Overviews). | togatech/vite.config.ts, togatech/scripts/prerender.mjs, togatech/src/routes.config.json, togatech/src/main.tsx, togatech/src/App.tsx, togatech/src/components/SEO/Seo.tsx, togatech/src/components/SEO/JsonLd.tsx, togatech/src/components/SEO/schema.ts, togatech/src/components/templates/AppLayout.tsx, togatech/src/pages/WhatWeDo/WhatWeDoPage.tsx, togatech/src/pages/WhatWeDo/viewModel/FIELDS/WHATWEDOPAGEFIELDS.json, togatech/src/pages/About/AboutPage.tsx, togatech/src/pages/Contact/ContactPage.tsx, togatech/src/pages/OurPlatform/OurPlatformPage.tsx, togatech/public/robots.prod.txt, togatech/public/sitemap.xml, togatech/public/llms.txt |
|
|
7
|
+
| [Creating Pull Requests on togatech](workflows/creating-pull-requests.md) | How to open a PR against `agilantsolutions/togatech`. | togatech/.git/config |
|
|
@@ -6,7 +6,7 @@ project: TOGA Technology Website
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-26
|
|
10
10
|
owners: ["ajean", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- togatech/vite.config.ts
|
|
@@ -17,6 +17,12 @@ files:
|
|
|
17
17
|
- togatech/src/components/SEO/Seo.tsx
|
|
18
18
|
- togatech/src/components/SEO/JsonLd.tsx
|
|
19
19
|
- togatech/src/components/SEO/schema.ts
|
|
20
|
+
- togatech/src/components/templates/AppLayout.tsx
|
|
21
|
+
- togatech/src/pages/WhatWeDo/WhatWeDoPage.tsx
|
|
22
|
+
- togatech/src/pages/WhatWeDo/viewModel/FIELDS/WHATWEDOPAGEFIELDS.json
|
|
23
|
+
- togatech/src/pages/About/AboutPage.tsx
|
|
24
|
+
- togatech/src/pages/Contact/ContactPage.tsx
|
|
25
|
+
- togatech/src/pages/OurPlatform/OurPlatformPage.tsx
|
|
20
26
|
- togatech/public/robots.prod.txt
|
|
21
27
|
- togatech/public/sitemap.xml
|
|
22
28
|
- togatech/public/llms.txt
|
|
@@ -37,7 +43,15 @@ Makes togatech.com visible and citable to search engines **and** AI answer engin
|
|
|
37
43
|
1. **robots:** `robotsPlugin` copies `robots.prod.txt` only when `mode === "production"`; beta/gamma/dev get `robots.dev.txt` (`Disallow: /`). `robots.prod.txt` carries an explicit AI-bot allow-list (GPTBot, ClaudeBot, PerplexityBot, …) + `https://` sitemap.
|
|
38
44
|
2. **prerender:** `npm run build` = `tsc && vite build && npx puppeteer browsers install chrome && node scripts/prerender.mjs` (the Chrome-install step was added 2026-06-19 — see gotchas). The script serves `dist/` (reading the original shell once, serving it for all nav routes), snapshots each route in headless Chrome, and writes `dist/<route>/index.html`. Runs in a real browser, so **no SSR/window guards needed**.
|
|
39
45
|
3. **meta:** each page renders `<Seo {...FIELDS.meta} />` — title/description/keywords/canonical/OG/Twitter from one place per page; canonical/OG URLs derived from one `SITE_URL`. `<JsonLd>` (Helmet `<script type="application/ld+json">`) is baked into the snapshot.
|
|
40
|
-
4. **structured data:**
|
|
46
|
+
4. **structured data:** all JSON-LD now lives in **one module — `src/components/SEO/schema.ts`** (the single source of truth), derived from `FOOTERFIELDS.json` (socials/email/phone) and each page's FIELDS JSON, so editing footer/content updates schema automatically. Exports and their render sites (emitted via `<JsonLd>`/Helmet):
|
|
47
|
+
- `organizationSchema` + `websiteSchema` → **AppLayout** (all pages).
|
|
48
|
+
- `serviceSchemas` (4 `Service` nodes) → **WhatWeDoPage**.
|
|
49
|
+
- `platformSchema` (`SoftwareApplication` for the TOGa Platform) → **OurPlatformPage**.
|
|
50
|
+
- `aboutPageSchema` (`AboutPage`) + `personSchemas` (per leader) → **AboutPage**.
|
|
51
|
+
- `contactPageSchema` (`ContactPage`) + `localBusinessSchemas` (per office, from `CONTACTPAGEFIELDS.map.locations`) → **ContactPage**.
|
|
52
|
+
- **Entity graph linked by `@id`:** Organization is `${SITE_URL}/#organization`, WebSite `/#website`, logo `/#logo`. Service/Person/LocalBusiness reference the org via `provider`/`worksFor`/`parentOrganization` `@id`; AboutPage/ContactPage set `isPartOf` the website and `about` the org. Use `@id` cross-references — do not inline duplicate org nodes.
|
|
53
|
+
- `organizationSchema.logo` is an `ImageObject` (`@id` `/#logo`, `url`, `contentUrl`, `caption "TOGA Technology"`); width/height are intentionally omitted because the logo is an SVG.
|
|
54
|
+
- `personSchemas` de-dupes a leader who appears in both `boardMembers` and `members` in `ABOUTPAGEFIELDS`, keyed on lowercased trimmed name (a `Set`) — naive emission produced duplicate `Person` nodes.
|
|
41
55
|
|
|
42
56
|
## Data model
|
|
43
57
|
None (static marketing site). All copy/meta/NAP/leaders come from `*FIELDS*.json`; structured data is derived from `FOOTERFIELDS.json`, `CONTACTPAGEFIELDS.json` (`map.locations`), and `ABOUTPAGEFIELDS.json` (`teamMembers`).
|
|
@@ -57,10 +71,15 @@ None — uniform.
|
|
|
57
71
|
- **og:image dedupe:** static OG/Twitter tags were removed from `index.html` because `<Seo>` emits per-page ones (Helmet appends rather than replacing pre-existing static tags → duplicates otherwise).
|
|
58
72
|
- **Verified data corrections:** real positioning is *integrated IT services + the ERA TOGa Platform*; real socials are LinkedIn `/toga-tech` + YouTube + Instagram (no Twitter/GitHub/Crunchbase); contact is `website@togatech.com` / `+1 (212) 736-0111`; **5** offices. Prior planning docs had several wrong specifics — verify schema/llms.txt against FIELDS before shipping.
|
|
59
73
|
- **Build size:** JS bundle is ~14.8 MB (4.9 MB gzip) — a real CWV/LCP risk; code-splitting is a separate follow-up.
|
|
74
|
+
- **FIELDS JSON is component-coupled — no free-prose body slot:** each page's `*FIELDS*.json` is shaped to specific components (hero, cardScroll, businessProblems, kpi, carousel), so there is **no place to drop a long prose block**. An audit's "rewrite to 900 words" is not structurally possible without building new components — you can only sharpen titles/descriptions/keywords/card copy that already have slots. (`/our-platform` was left unchanged: it is already content-dense — 7 app modules + 4 stacks + testimonials + KPIs — and the original audit under-counted because that content is JS-rendered.)
|
|
75
|
+
- **`personSchemas` duplicate guard:** a leader can be in both `boardMembers` and `members` in `ABOUTPAGEFIELDS` — `personSchemas` must de-dup by lowercased trimmed name, or it emits duplicate `Person` nodes.
|
|
76
|
+
- **🔴 Brand name is "TOGA Technology" only — never "Agilant":** an earlier draft adding `alternateName: "Agilant"` / "(formerly Agilant)" to the org schema was explicitly reverted. Do not reintroduce "Agilant" in schema, copy, or meta. (Captured as a team brand standard — see Related docs.)
|
|
60
77
|
|
|
61
78
|
## Change history
|
|
79
|
+
- 2026-06-26 — SEO improvement effort (PRs #33–#36 → `_production`; SEO health ~61→78 est.). Centralized all JSON-LD into `src/components/SEO/schema.ts` (org/website/platform/service×4/about/contact/localBusiness/person, `@id`-linked graph), de-duped Person nodes, logo→`ImageObject`, sharpened `/what-we-do` meta+copy, added `<lastmod>` to all 7 sitemap URLs. Recorded brand constraint ("TOGA Technology" only, never "Agilant") and the component-coupled-FIELDS limit. (ajean)
|
|
62
80
|
- 2026-06-19 — Merged SEO/AEO into `_production` (resolved ContactPage conflict: reCAPTCHA + Seo/JsonLd, dropped Helmet). Fixed Amplify build: committed `package-lock.json` (npm ci EUSAGE) and added `npx puppeteer browsers install chrome` to the build before prerender. (jcardinal)
|
|
63
81
|
- 2026-06-18 — Initial SEO/AEO/GEO implementation: robots mode-gate + AI allow-list + sitemap/llms; Puppeteer prerender (fail-fast); shared `<Seo>` single-source meta + canonical; JSON-LD (Organization/WebSite/Breadcrumb/SoftwareApplication/LocalBusiness×5/Person×12). PRs #29 + #30, pending merge. (ajean)
|
|
64
82
|
|
|
65
83
|
## Related docs
|
|
66
84
|
- standalone/apps/togatech/architecture.md (update its Build/deploy section once PR #30 merges — robotsPlugin gating + prerender step).
|
|
85
|
+
- standalone/apps/togatech/workflows/creating-pull-requests.md — how PRs get opened on this repo (gh CLI is unauthenticated; uses curl + osxkeychain token; base `_production`).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Creating Pull Requests on togatech
|
|
3
|
+
framework: "standalone"
|
|
4
|
+
repo: togatech
|
|
5
|
+
project: TOGA Technology Website
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: ["ajean"]
|
|
11
|
+
files:
|
|
12
|
+
- togatech/.git/config
|
|
13
|
+
related:
|
|
14
|
+
- standalone/apps/togatech/features/seo-aeo-geo-prerender.md
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Summary
|
|
18
|
+
How to open a PR against `agilantsolutions/togatech`. The local `gh` CLI is **not usable**
|
|
19
|
+
for this repo (it lacks the `read:org` scope), so PRs are created with a raw GitHub REST
|
|
20
|
+
call authenticated by the token already sitting in the macOS keychain. **Base branch is
|
|
21
|
+
`_production`, not `_main`** (`_main` is stale) — feature branches are cut from `_production`.
|
|
22
|
+
|
|
23
|
+
## How it works
|
|
24
|
+
1. Branch off `_production` (e.g. `feature/seo-schema`), commit, and push the branch.
|
|
25
|
+
2. Pull the GitHub token from the osxkeychain credential helper rather than relying on `gh`:
|
|
26
|
+
```
|
|
27
|
+
TOKEN=$(printf 'protocol=https\nhost=github.com\n\n' | git credential fill | sed -n 's/^password=//p')
|
|
28
|
+
```
|
|
29
|
+
3. POST to the PR API with that token:
|
|
30
|
+
```
|
|
31
|
+
curl -s -X POST \
|
|
32
|
+
-H "Authorization: token $TOKEN" \
|
|
33
|
+
-H "Accept: application/vnd.github+json" \
|
|
34
|
+
https://api.github.com/repos/agilantsolutions/togatech/pulls \
|
|
35
|
+
-d '{"title":"...","head":"<branch>","base":"_production","body":"..."}'
|
|
36
|
+
```
|
|
37
|
+
4. The response JSON contains `html_url` for the new PR. Merge follows the normal review flow.
|
|
38
|
+
|
|
39
|
+
## Gotchas / known issues
|
|
40
|
+
- **`gh pr create` fails** here — the authenticated `gh` session lacks `read:org`. Do not
|
|
41
|
+
burn time re-authing `gh`; use the curl path above.
|
|
42
|
+
- **Base is `_production`.** Targeting `_main` opens a PR against a stale branch. (This matches
|
|
43
|
+
the `worker2` mainline convention — mainline is `_production`, not `_main`.)
|
|
44
|
+
- **Never echo `$TOKEN`** into logs, commit messages, or PR bodies — it is a live credential.
|
|
45
|
+
|
|
46
|
+
## Change history
|
|
47
|
+
- 2026-06-26 — Documented the curl-based PR flow (gh unauthenticated; base `_production`), captured during the SEO improvement effort (PRs #33–#36). (ajean)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Brand Naming — "TOGA Technology" only
|
|
3
|
+
framework: "standalone"
|
|
4
|
+
repo: togatech
|
|
5
|
+
project: TOGA Technology Website
|
|
6
|
+
client: shared
|
|
7
|
+
type: standard
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-26
|
|
10
|
+
owners: ["ajean"]
|
|
11
|
+
files:
|
|
12
|
+
- togatech/src/components/SEO/schema.ts
|
|
13
|
+
related:
|
|
14
|
+
- standalone/apps/togatech/features/seo-aeo-geo-prerender.md
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Brand Naming — "TOGA Technology" only
|
|
18
|
+
|
|
19
|
+
The company is referenced **only** as "TOGA Technology" in all code, schema, meta, and
|
|
20
|
+
copy. **Never** use "Agilant" — not as a brand name, not as `alternateName`, and not in
|
|
21
|
+
any "(formerly Agilant)" framing.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
During the 2026-06 SEO effort an earlier draft added `alternateName: "Agilant"` and a
|
|
25
|
+
"(formerly Agilant)" note to the Organization JSON-LD and org description. This was
|
|
26
|
+
**explicitly reverted by the owner**. Surfacing the legacy name in structured data and
|
|
27
|
+
copy is off-brand and creates the wrong entity association for search/AI answer engines.
|
|
28
|
+
|
|
29
|
+
## Applies to
|
|
30
|
+
- Schema.org JSON-LD (`src/components/SEO/schema.ts`) — no `alternateName: "Agilant"`.
|
|
31
|
+
- Page meta (titles, descriptions), on-page copy, `llms.txt`, and any FIELDS JSON.
|
|
32
|
+
- Any future SEO/content/AEO work on togatech.
|
|
33
|
+
|
|
34
|
+
## Change history
|
|
35
|
+
- 2026-06-26 — Standard recorded after reverting an "Agilant" alternateName/"formerly" draft in the org schema. (ajean)
|
package/package.json
CHANGED