diff --git a/packages/overview/02-modules-and-repos.md b/packages/overview/02-modules-and-repos.md index 8701aff..5becb76 100644 --- a/packages/overview/02-modules-and-repos.md +++ b/packages/overview/02-modules-and-repos.md @@ -34,6 +34,7 @@ Each runtime piece is its **own git repo** on Forgejo (`git.georgelambert.org`, | **zapier-user-docs** | `packages/zapier-user-docs` | Customer signup → register → lookup | | **UI-Docs** | `packages/ui-docs` | Operator/staff/portal walkthrough, screenshots, review PDF | | **verae-staff-session** | `packages/verae-staff-session` | Shared staff cookie login for department HTML | +| **verae-staff-ui** | `packages/verae-staff-ui` | Shared staff review HTML (CS + access-staff) | | **zapier-docs-master** | `packages/docs-master` | Per-module `SUMMARY.md` + `NATS.md` | | **verae-ops** | `packages/verae-ops` | Docker, Proxmox, VMs, dedicated hardware, linking services | diff --git a/packages/ui-docs/REPORT.md b/packages/ui-docs/REPORT.md index 2a71711..275c6df 100644 --- a/packages/ui-docs/REPORT.md +++ b/packages/ui-docs/REPORT.md @@ -17,11 +17,13 @@ Follow-up to the 2026-09-11 review PDF. Items the review listed as open are now ## Still to consider -| Severity | Issue | Suggestion | -|----------|-------|------------| -| Med | Swagger `/docs` remains stock | Keep it for integrators; do not skin. Portal already labels it as stock OpenAPI explorer. | -| Low | access-staff and CS HTML are twins | Share one template file if those packages ever merge. | -| Low | Staff cookie is host-scoped (`127.0.0.1`) | Fine on one operator host. For multiple DNS names, put a reverse proxy in front. | +None of the previous review leftovers are open. + +| Severity | Issue | What shipped | +|----------|-------|----------------| +| Med | Swagger `/docs` stock | Left as vendor Swagger. Title + banner: “OpenAPI explorer — stock Swagger UI for integrators.” Not skinned. | +| Low | access-staff and CS HTML twins | Shared template in `verae-staff-ui` (`review.html` + tokens). Both servers load it in the monorepo. | +| Low | Staff cookie host-scoped | `STAFF_COOKIE_DOMAIN` / `STAFF_COOKIE_SECURE` plus nginx example on `verae-staff-session`. | ## How to use each surface diff --git a/packages/ui-docs/UI-REVIEW.pdf b/packages/ui-docs/UI-REVIEW.pdf index 924b0ba..1cf4b6d 100644 --- a/packages/ui-docs/UI-REVIEW.pdf +++ b/packages/ui-docs/UI-REVIEW.pdf @@ -527,7 +527,7 @@ endobj endobj 56 0 obj << -/Author (UI-Docs) /CreationDate (D:20260911181807-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260911181807-04'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (UI-Docs) /CreationDate (D:20260911182248-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260911182248-04'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (\(unspecified\)) /Title (Verae Time \327 Zapier UI review) /Trapped /False >> endobj @@ -540,10 +540,10 @@ endobj endobj 58 0 obj << -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2126 +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2109 >> stream -Gau0DgN)%,&:O:Sm%`=?BS&0?p@Q1\lc-'UVNoTTF>D)X@HTMnPWD]5'l"?Hg)q-.2HC3kf"?!h.Aj7:&Bg/,E[tnF=:%rCQ9Ej9=QlB&of`'i(]1*)q0+4H:i+;_4b7d/;W#aM81qS]'"bS9RUQ&$)eD3f`_%KOuMNrZN14BI6A<\+CK?V.b\A#K`@sh'E3]8?quFAfk^[XVKhaCm+QBGu59]E(jRX`6kBmF=06^]f6KM%'21nK\_d[)[u$C-Et@r)J8*(\T_T]j?t%r";EhV"jT;t"C@N"fUh2H\C2E>[;&BbrTN)mVp#6cOu2$po/3EA'Xm9\R^i+r7&?6mkTa8H^(0m\YonDIK.QrCP)&dd'>h:[/;hL95\F\29k6b%8@"cUo'Sr4a=X^4>AVM@t>NnStGe_L&$H5hm;j??;9[O1bXO%-dOPZr<1`_+%#9*gc_83A(k`,BP`q)U!3^H5lY(A'^73"LQOt#cZ7r$ah-'bSn!\(UDW!Y(7PK39)S0=9jr/(BVCG[qZ!nb$>8`/On>1%[!2e>ITk[>KR:Jk2=i[,IW5I_4"Tf!#2S;mADl^f6Wc_YLUaZ;XX1Y8.+O'J][\up]@]ja\1XXD4CE8D_GfN[XbemM;3_E2p*G49etYa'c[it%=l"H*I)BO>$`m@2]NSF"m9R*XT`&5=/]tFT2#ZK;'f,aOU;TEc7UaK5'o58r>fr,l,L.Q/Q[r$>9gP24VGFNeB[Ut7,BGN`%%Q@FJHY_dSSe>Yf#<^G<@X^@ebO_HIWbVCR&ghEd_b/c79`LqA^'AR'H'WDHM*lh"eGJ+VrS)ljU^"VN8\1Q#JrEANNh1`o)ima_RIXQh!Ve6gu)iY)T*!YF>$1ap.:YV2[HI5V?.SfU2]-Au*J:0jfDhf1lj5SS6#pW^M@#hJsdbTB)msC6X8X#5u[--H`Os_\>oVd6`rdr)X;i)#4V[dIXDA(\A7qpbIjh$dRd+\BDc!IJoiDb?l:pN=OZ8dCncOfGPo3&%YAD?#)khAiUT]69_G;ASI,l*l:fY%[LLj[hkc]EFE%,:;EIu+S<(WD$9Yt)"p=brMWb;HR\@c>s1T2MgsRFE`d(MUOrnFE;>L"s*fXmMMERJ1;&gH^9J?dIS6\W4LBi;PAirY_%tQWC\7q,%;]X-a&:>*,RI&ZU,b_JkJIDMLoil=-\o'Dim,p^ER-KufK^8_<7F'Jn/Tb$'fkL4R?]-dIHYh%%G#8+D9R0@5uq&ZVH/X9'HV6%^Y&[!LoM9\Uendstream +Gau0CCK&tI'`H=\\0#F?(sB*B0`-F/K^O:1lW)UVV!"[J;fSi]*SbYea]\G0@`M"L_L$+:@"PR$F6$hQlO41V%iP:uDlm>^S(/]JKpK4j7Vem5Z5qCDmo]*`RIR"2&f>788!nnQ(BD!GA*lL=RM/sF?B!s#W?i!0c4B$&2*ju\nskOKe"c)eb4JKH.#J'_]LUN_]dCG6r2Pu`UkUIl-%lU#4=86(dc)V&N\,D/E0IP%gdEc--jo(*09fu>dRW%i"=obkTq2&9pOMddFNG2u/t5UCrbGV#tApd_Xt/'1HfhCV%JrM\kn;SF1]k_)=VUAX.#4M@"CAnD>`KQ`"c-3*FJOeGgG9!4.UH:.R4bSk$"4]GlcdC9HZI@3Xn-ek?>?gWCd_"V&:"_:.U`iGW.*_X_Cgg2nX.H>;`VbON>HD`1_60#8Q@s#e)BUB,Z:kc*&[7--mpD!luAX3c;m1PAP<,^*3adH/;cn%j/p;("lRA,FRS"Ufi4sAn%\#(nnO.fc1pP6J+)i>%U^\m,OXGks%kl#k#Ca\S1\-e^/`>)EL%Vki@1%_\Ms?$)Fo"]eGHEm]bC&2-A^)IAAX:b-,Vd)/jOT4DZteI\LSIJBV`aE#Zg9<7Y$iC?k";;Am>iO9->:TU,P"@OYO;!Vk"&DFX[CZCDX>f/ChG8N".!K:l5LYeHZ-ha@Ypj0R_O@?6T'jO8CDqZl:31_gBp>>I]U_4>B''$DU>q!SP"M6[?%h!ZP7lrg^,?u0^?0&dQbct6,_4oqi`gVbG0E$Ro<822JA=?J]*55okI3ILi7F"?M/[N;POp$c01[C[$-?BJt-GmPX$3Jk[(Uc*=sTb#RJhJfF2#+o+UFLrC(,+/q-`Uc^`$ZI'jK=#<']/Pp=FO&8d1pib,Zmkd8Yc$e3[gLT6WJg5=\'SKP,md*W\sI#^AF8)3N8nR1'+>ock\R5*:<=u+3jl;q*%5!kl2f>A!WkStRsNk5\Nia940@Aljl].S.mEFTo"#Y6N"PnE9*SP$8UYl(ZqhL@o%GAjI@hr5p?>s`K^F"Til2b'rFLl9"c&+J-A\#%&tbT659:h_pp]"DB91>Q]slj;QWp&-FNUf\d/'in"n>(\c9i(ori7%0M/,oAQHc.;,o%/mq=3=(QVtVJU8tbi)!5>geD:jbf^R`\`\'g6oAOH@)oP=:K]Jgin#*BP8C=,$X#K#c/;=ZehS2'&W>;RR@dDr#NSC[b2tDL$.h(7iMmX+&#Ug]cp(&=)'6s*M_sV>iBJkT^qtgH$Y2@dn(M])iZbEkg)j98p[U=jH)T:f/K/\=i468eJ*jLPF<`_O;l4-t8ALtajO>etd9c<_/6qJ:+Ge[ujj(YHBAfruJ"K_,03_^V:$6?21=s!aUJYj98]gKQ.b+B0(W_4#"8>cI16'<$p3n?Muo^),F<91,T+endstream endobj 59 0 obj << @@ -781,35 +781,35 @@ xref 0005443418 00000 n 0005443712 00000 n 0005443952 00000 n -0005446170 00000 n -0005446937 00000 n -0005447568 00000 n -0005448168 00000 n -0005448760 00000 n -0005449360 00000 n -0005449957 00000 n -0005450542 00000 n -0005451130 00000 n -0005451748 00000 n -0005452343 00000 n -0005452930 00000 n -0005453510 00000 n -0005454082 00000 n -0005454654 00000 n -0005455247 00000 n -0005455844 00000 n -0005456414 00000 n -0005456989 00000 n -0005457585 00000 n -0005458170 00000 n -0005458738 00000 n -0005459344 00000 n -0005459924 00000 n -0005460519 00000 n +0005446153 00000 n +0005446920 00000 n +0005447551 00000 n +0005448151 00000 n +0005448743 00000 n +0005449343 00000 n +0005449940 00000 n +0005450525 00000 n +0005451113 00000 n +0005451731 00000 n +0005452326 00000 n +0005452913 00000 n +0005453493 00000 n +0005454065 00000 n +0005454637 00000 n +0005455230 00000 n +0005455827 00000 n +0005456397 00000 n +0005456972 00000 n +0005457568 00000 n +0005458153 00000 n +0005458721 00000 n +0005459327 00000 n +0005459907 00000 n +0005460502 00000 n trailer << /ID -[<2c41c692d1ec22d395f7c56711e5eca1><2c41c692d1ec22d395f7c56711e5eca1>] +[] % ReportLab generated PDF document -- digest (opensource) /Info 56 0 R @@ -817,5 +817,5 @@ trailer /Size 84 >> startxref -5461285 +5461268 %%EOF diff --git a/packages/ui-docs/scripts/build-review-pdf.py b/packages/ui-docs/scripts/build-review-pdf.py index d000dc5..8cb96c1 100644 --- a/packages/ui-docs/scripts/build-review-pdf.py +++ b/packages/ui-docs/scripts/build-review-pdf.py @@ -133,10 +133,10 @@ def main(): story.append(p("Issues still to consider", s["h1"])) remaining = [ - ["Severity", "Issue", "Suggestion"], - ["Med", "Swagger /docs remains stock.", "Keep it for integrators; do not skin it. Portal already labels it stock OpenAPI explorer."], - ["Low", "access-staff and CS HTML are twins.", "Share one template if those packages ever merge."], - ["Low", "Staff cookie is host-scoped (127.0.0.1).", "Fine on one operator host. Multiple DNS names need a reverse proxy."], + ["Severity", "Issue", "What shipped"], + ["Med", "Swagger /docs remains stock (intentional).", "Left unskinned. Banner: OpenAPI explorer for integrators. Use x-api-key."], + ["Low", "access-staff and CS HTML were twins.", "Shared template package verae-staff-ui."], + ["Low", "Staff cookie was host-scoped.", "STAFF_COOKIE_DOMAIN plus reverse-proxy example in verae-staff-session."], ] body = getSampleStyleSheet()["BodyText"] body.fontSize = 8 diff --git a/packages/verae-access-staff/src/server.js b/packages/verae-access-staff/src/server.js index 171f156..a517f1e 100644 --- a/packages/verae-access-staff/src/server.js +++ b/packages/verae-access-staff/src/server.js @@ -5,6 +5,7 @@ import http from 'node:http'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { listCustomers, withCustomerName } from './names.js'; +import { staffPageHtml } from './staff-page.js'; const PUBLIC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'public'); const PORT = Number(process.env.PORT || 3025); @@ -41,7 +42,16 @@ const server = http.createServer(async (req, res) => { } } res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); - res.end(fs.readFileSync(path.join(PUBLIC, 'index.html'))); + res.end( + await staffPageHtml( + { + title: 'Staff access', + kicker: 'staff plane · after authz', + lede: 'CS / sales / accounting door. Review and credit go through authz, then account-balance. Amounts are dollars.', + }, + path.join(PUBLIC, 'index.html'), + ), + ); return; } if (req.method === 'GET' && url.pathname === '/health') { diff --git a/packages/verae-access-staff/src/staff-page.js b/packages/verae-access-staff/src/staff-page.js new file mode 100644 index 0000000..88ec449 --- /dev/null +++ b/packages/verae-access-staff/src/staff-page.js @@ -0,0 +1,16 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export async function staffPageHtml(opts, fallbackPath) { + const sibling = path.join(path.dirname(fallbackPath), '..', '..', 'verae-staff-ui', 'src', 'load.js'); + try { + if (fs.existsSync(sibling)) { + const { loadReviewHtml } = await import(pathToFileURL(sibling).href); + return loadReviewHtml(opts, fallbackPath); + } + } catch { + /* standalone clone */ + } + return fs.readFileSync(fallbackPath, 'utf8'); +} diff --git a/packages/verae-staff-session/README.md b/packages/verae-staff-session/README.md index 38fb73c..baebc4b 100644 --- a/packages/verae-staff-session/README.md +++ b/packages/verae-staff-session/README.md @@ -7,3 +7,25 @@ Shared cookie login for CS / sales / accounting / access-staff HTML. Port `:3027`. Set `STAFF_AUTH=1` on the department servers and `STAFF_SESSION_URL=http://127.0.0.1:3027`. Cookie host is the browser host (ports share `127.0.0.1`). JSON APIs stay open unless you also send `x-staff-key`. Default key: `STAFF_KEY` or `ADMIN_KEY` or `admin-dev-key`. + +## Multiple hostnames + +Cookies are host-scoped. On one operator box (`127.0.0.1`) that is enough. For several DNS names, put one reverse proxy in front and set `STAFF_COOKIE_DOMAIN`: + +```nginx +server { + server_name staff.example.com; + location /session/ { proxy_pass http://127.0.0.1:3027/; } + location /cs/ { proxy_pass http://127.0.0.1:3011/; } + location /sales/ { proxy_pass http://127.0.0.1:3012/; } + location /acct/ { proxy_pass http://127.0.0.1:3013/; } + location /staff/ { proxy_pass http://127.0.0.1:3025/; } +} +``` + +```bash +STAFF_COOKIE_DOMAIN=.example.com +STAFF_COOKIE_SECURE=1 +STAFF_SESSION_URL=https://staff.example.com/session +STAFF_AUTH=1 +``` diff --git a/packages/verae-staff-session/src/token.js b/packages/verae-staff-session/src/token.js index 580e52b..6d3d029 100644 --- a/packages/verae-staff-session/src/token.js +++ b/packages/verae-staff-session/src/token.js @@ -9,7 +9,11 @@ export function sessionToken() { } export function cookieHeader() { - return `staff_session=${sessionToken()}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`; + let s = `staff_session=${sessionToken()}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`; + const domain = process.env.STAFF_COOKIE_DOMAIN; + if (domain) s += `; Domain=${domain}`; + if (process.env.STAFF_COOKIE_SECURE === '1') s += '; Secure'; + return s; } export function cookieOk(req) { diff --git a/packages/verae-staff-session/test/token.test.js b/packages/verae-staff-session/test/token.test.js index 503ed7b..38177fe 100644 --- a/packages/verae-staff-session/test/token.test.js +++ b/packages/verae-staff-session/test/token.test.js @@ -1,6 +1,6 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { sessionToken, cookieOk, headerOk } from '../src/token.js'; +import { sessionToken, cookieOk, headerOk, cookieHeader } from '../src/token.js'; test('cookie matches HMAC of staff key', () => { const tok = sessionToken(); @@ -9,3 +9,11 @@ test('cookie matches HMAC of staff key', () => { assert.equal(cookieOk({ headers: { cookie: 'staff_session=nope' } }), false); assert.equal(headerOk({ headers: { 'x-staff-key': process.env.STAFF_KEY || 'admin-dev-key' } }), true); }); + +test('cookie Domain is optional', () => { + delete process.env.STAFF_COOKIE_DOMAIN; + assert.equal(cookieHeader().includes('Domain='), false); + process.env.STAFF_COOKIE_DOMAIN = '.example.com'; + assert.match(cookieHeader(), /Domain=\.example.com/); + delete process.env.STAFF_COOKIE_DOMAIN; +}); diff --git a/packages/verae-staff-ui/NATS.md b/packages/verae-staff-ui/NATS.md new file mode 100644 index 0000000..1794266 --- /dev/null +++ b/packages/verae-staff-ui/NATS.md @@ -0,0 +1,3 @@ +# NATS + +None. Static HTML template. diff --git a/packages/verae-staff-ui/README.md b/packages/verae-staff-ui/README.md new file mode 100644 index 0000000..3a8af5f --- /dev/null +++ b/packages/verae-staff-ui/README.md @@ -0,0 +1,7 @@ +# verae-staff-ui + +One review HTML template for **customer-service** and **access-staff** (they were twins). Tokens: `{{TITLE}}`, `{{KICKER}}`, `{{LEDE}}`. + +In the monorepo both servers load `packages/verae-staff-ui/public/review.html`. A cloned package can set `STAFF_UI_HTML` or keep a local `public/index.html` fallback. + +**Forgejo:** https://git.georgelambert.org/marchon/verae-staff-ui diff --git a/packages/verae-staff-ui/SUMMARY.md b/packages/verae-staff-ui/SUMMARY.md new file mode 100644 index 0000000..a07381b --- /dev/null +++ b/packages/verae-staff-ui/SUMMARY.md @@ -0,0 +1,3 @@ +# verae-staff-ui + +Shared staff account-review page (dollars, names typeahead, credit form) used by CS and the staff access plane. diff --git a/packages/verae-staff-ui/package.json b/packages/verae-staff-ui/package.json new file mode 100644 index 0000000..677ffd6 --- /dev/null +++ b/packages/verae-staff-ui/package.json @@ -0,0 +1,8 @@ +{ + "name": "verae-staff-ui", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Shared staff review HTML for CS and access-staff", + "scripts": { "test": "node --test test/*.test.js" } +} diff --git a/packages/verae-staff-ui/public/review.html b/packages/verae-staff-ui/public/review.html new file mode 100644 index 0000000..c993109 --- /dev/null +++ b/packages/verae-staff-ui/public/review.html @@ -0,0 +1,157 @@ + + + + + + {{TITLE}} + + + + + +
+
{{KICKER}}
+

{{TITLE}}

+

{{LEDE}}

+
+
+
+
+
+ + + +
+ +
+
+
+

Apply credit

+
+
+ + +
+
+ + +
+
+ + +
+ +
+

Credits write to account-balance. The customer sees dollars, not cents.

+
+
+
+ + + diff --git a/packages/verae-staff-ui/src/load.js b/packages/verae-staff-ui/src/load.js new file mode 100644 index 0000000..2998152 --- /dev/null +++ b/packages/verae-staff-ui/src/load.js @@ -0,0 +1,27 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); + +export function reviewHtmlPath(fallback) { + const env = process.env.STAFF_UI_HTML; + const shared = path.join(HERE, '..', 'public', 'review.html'); + const sibling = path.join(HERE, '..', '..', 'verae-staff-ui', 'public', 'review.html'); + for (const p of [env, shared, sibling, fallback]) { + if (p && fs.existsSync(p)) return p; + } + return fallback; +} + +export function loadReviewHtml(opts, fallback) { + const file = reviewHtmlPath(fallback); + let html = fs.readFileSync(file, 'utf8'); + const map = { + '{{TITLE}}': opts.title || 'Staff review', + '{{KICKER}}': opts.kicker || 'staff', + '{{LEDE}}': opts.lede || 'Review prepaid balance, credits, usage, and payments in dollars.', + }; + for (const [k, v] of Object.entries(map)) html = html.split(k).join(v); + return html; +} diff --git a/packages/verae-staff-ui/test/load.test.js b/packages/verae-staff-ui/test/load.test.js new file mode 100644 index 0000000..a3796df --- /dev/null +++ b/packages/verae-staff-ui/test/load.test.js @@ -0,0 +1,11 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { loadReviewHtml } from '../src/load.js'; + +test('fills title kicker lede', () => { + const html = loadReviewHtml({ title: 'Staff access', kicker: 'staff plane', lede: 'After authz.' }); + assert.match(html, /Staff access/); + assert.match(html, /staff plane/); + assert.match(html, /After authz/); + assert.doesNotMatch(html, /\{\{TITLE\}\}/); +}); diff --git a/packages/zapier-decisions/LOG.md b/packages/zapier-decisions/LOG.md index 1c15474..49cd044 100644 --- a/packages/zapier-decisions/LOG.md +++ b/packages/zapier-decisions/LOG.md @@ -32,6 +32,12 @@ - Disable lan-134 unless `FLEET_ENABLE_LAN134=1`. - SSH spawn timeout 8s; failed hosts skipped. +## 2026-09-11 — last three UI leftovers + +- Swagger `/docs` stays stock; banner names it OpenAPI explorer. +- Shared template `verae-staff-ui` for CS + access-staff. +- `STAFF_COOKIE_DOMAIN` + nginx example for multi-host. + ## 2026-09-11 — names, staff session, exclusive jobs.events - Account-balance stores display names (`customer.put` + lookup by name). Edge writes names on customer create/edit; CS/sales/accounting/staff join from edge if the ledger has no name. diff --git a/packages/zappier-customer-service/src/server.js b/packages/zappier-customer-service/src/server.js index 6d0c0aa..8f069fa 100644 --- a/packages/zappier-customer-service/src/server.js +++ b/packages/zappier-customer-service/src/server.js @@ -8,6 +8,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { SUBJECTS, billingRequest } from './nats-billing.js'; import { listCustomers, withCustomerName } from './names.js'; +import { staffPageHtml } from './staff-page.js'; const PUBLIC = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'public'); @@ -44,7 +45,16 @@ const server = http.createServer(async (req, res) => { } } res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' }); - res.end(fs.readFileSync(path.join(PUBLIC, 'index.html'))); + res.end( + await staffPageHtml( + { + title: 'Customer service', + kicker: 'staff · customer service', + lede: 'Look up a customer by name. Review prepaid balance, credits, usage, and payments. Credit amounts are in dollars.', + }, + path.join(PUBLIC, 'index.html'), + ), + ); return; } if (req.method === 'GET' && url.pathname === '/health') { diff --git a/packages/zappier-customer-service/src/staff-page.js b/packages/zappier-customer-service/src/staff-page.js new file mode 100644 index 0000000..88ec449 --- /dev/null +++ b/packages/zappier-customer-service/src/staff-page.js @@ -0,0 +1,16 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { pathToFileURL } from 'node:url'; + +export async function staffPageHtml(opts, fallbackPath) { + const sibling = path.join(path.dirname(fallbackPath), '..', '..', 'verae-staff-ui', 'src', 'load.js'); + try { + if (fs.existsSync(sibling)) { + const { loadReviewHtml } = await import(pathToFileURL(sibling).href); + return loadReviewHtml(opts, fallbackPath); + } + } catch { + /* standalone clone */ + } + return fs.readFileSync(fallbackPath, 'utf8'); +} diff --git a/packages/zappier/src/app.ts b/packages/zappier/src/app.ts index a6e9efa..a8c2c8a 100644 --- a/packages/zappier/src/app.ts +++ b/packages/zappier/src/app.ts @@ -120,7 +120,16 @@ export function buildApp(deps: AppDeps = {}): { app.use(express.json()); const spec = YAML.load(SPEC_PATH); - app.use('/docs', swaggerUi.serve, swaggerUi.setup(spec)); + app.use( + '/docs', + swaggerUi.serve, + swaggerUi.setup(spec, { + customSiteTitle: 'OpenAPI explorer (stock Swagger UI)', + customCss: + 'body::before{content:"OpenAPI explorer — stock Swagger UI for integrators. Not a customer surface. Use x-api-key.";' + + 'display:block;background:#312e81;color:#eef0fe;font:650 13px -apple-system,sans-serif;padding:.7rem 1.2rem}', + }), + ); app.use( '/admin/api', diff --git a/scripts/build-docs-site.py b/scripts/build-docs-site.py index abd7329..0358140 100755 --- a/scripts/build-docs-site.py +++ b/scripts/build-docs-site.py @@ -366,6 +366,7 @@ def main() -> None: "zapier-decisions", "ui-docs", "verae-staff-session", + "verae-staff-ui", ): pkg_root = ROOT / "packages" / pkg if pkg in {"zapier-user-docs", "overview", "docs-master", "verae-ops"}: diff --git a/scripts/gen-module-docs.py b/scripts/gen-module-docs.py index 9acf1a4..3992a8f 100644 --- a/scripts/gen-module-docs.py +++ b/scripts/gen-module-docs.py @@ -69,6 +69,7 @@ REPO_READMES = [ "zapier-decisions", "ui-docs", "verae-staff-session", + "verae-staff-ui", ] SKIP_PARTS = {"test", "tests", "node_modules", "dist"} diff --git a/scripts/push-module-repos.sh b/scripts/push-module-repos.sh index 1187b7b..19d3ca8 100755 --- a/scripts/push-module-repos.sh +++ b/scripts/push-module-repos.sh @@ -66,6 +66,7 @@ create verae-nats-accounts "NATS INTERNAL vs LEAF account policy" create zapier-decisions "Architecture decisions and action log" create UI-Docs "UI walkthrough, screenshots, and review PDF" create verae-staff-session "Shared staff cookie login for department HTML" +create verae-staff-ui "Shared staff review HTML template" push_dir "$ROOT/packages/zappier" zappier-edge push_dir "$ROOT/packages/verae-zapier-middleware" verae-middleware @@ -98,5 +99,6 @@ push_dir "$ROOT/packages/verae-nats-accounts" verae-nats-accounts push_dir "$ROOT/packages/zapier-decisions" zapier-decisions push_dir "$ROOT/packages/ui-docs" UI-Docs push_dir "$ROOT/packages/verae-staff-session" verae-staff-session +push_dir "$ROOT/packages/verae-staff-ui" verae-staff-ui echo ALL_MODULE_REPOS_PUSHED