Skip to content

Align with the Pacific Data Hub visual identity - #1

Open
gvdr wants to merge 26 commits into
mainfrom
pdh-visual
Open

Align with the Pacific Data Hub visual identity#1
gvdr wants to merge 26 commits into
mainfrom
pdh-visual

Conversation

@gvdr

@gvdr gvdr commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Aligns the app with the Pacific Data Hub visual identity (Guidelines V1.0), and
brands Country Snapshots as a sibling product rather than a section of Data
Surfer.

Charts are deliberately untouched. Dashboards still render in the existing
chart palette. Three brand colours cannot separate eight series, so data colour
needs its own accessible categorical scale designed to sit beside the brand
rather than be drawn from it. That is a design decision rather than an
implementation, and it is the one place where "align to the standard" and "keep
the data readable" genuinely conflict. Everything here is coherent without it.

What changed

Palette and type. Tokens move to PDH: #223b83, #00a6c8, #e37b0a.
Derived tints were recomputed by carrying each one's offset from its anchor
rather than being re-picked by eye. Display type moves from Montserrat to Inter,
because that is what the PDH website uses — verified against both the guidelines
(p26, p27) and the live site, which serves no Trade Gothic at all. Trade Gothic
appears only as outlined SVG in the wordmarks, which is also the licence-clean
split: desktop licences cover outlining a logo, not @font-face.

Wordmarks. DATA SURƒER and C⊙UNTRY SNAPSHOTS, following the convention
of .STAT EXPL⊙RER and PACIFIC MΛP. The florin is Trade Gothic's own U+0192,
not an approximation. scripts/build-wordmarks.py regenerates both; adding a
third tool is a table entry.

Pattern. Carried through header, hero, footer, page surface and empty states
via BrandField. One rule governs it: the pattern goes around content, never
under it
. An earlier version said "density runs inversely to information
density", which sounds similar and permitted a strong pattern behind the home
page headline, dropping it to an unreadable contrast.

Pictograms. The fifteen PDH domain icons are wired to the categories the
dataflow index already carries. Five of ten categories map; the rest return
null rather than borrow a near-miss, since XDO and IND have no icon that
means them and SDG/BP50/NMDI are frameworks rather than subjects.

Country Snapshots. 49 hardcoded Oceanic colours moved to tokens, and it now
wears the PDH bar with its own wordmark and its own home link.

Renames. SDMX SurferData Surfer (30 user-facing strings) and prose
SDMXSDMx (42). Identifiers keep their spelling. Note this edits both
system prompts, so the Anthropic prompt cache misses once per prompt after
deploy.

A 404 page, which did not exist — Next served its bare default.

Accessibility

An audit (scripts/audit-contrast.py, kept) found four contrast failures that
had already shipped:

1.1:1 a badge that was light orange on light orange
2.4:1 search placeholders at 50% opacity
2.9:1 the brand cyan as text on white, 18 occurrences
4.2:1 a grey that passes on white and fails on the surface it is used on

Also fixed: the hero gradient put white text at 2.9:1 (it now runs dark where the
copy sits), nothing defined a focus-visible style while six components applied
outline-none, and 26 animate-* utilities had no reduced-motion handling.

What reviewers should look at

The whole branch was built and verified headless — typecheck, lint, 249 tests,
production build — and only reviewed visually at the end. Visual review is the
thing being asked for
, not correctness review.

Three questions are open by choice:

  1. Snapshot pages carry both the PDH bar and the page shell's own title header.
    One bar too many, or correct?
  2. Category badges are a rainbow of Tailwind hues. Now that a pictogram carries
    the domain, the colour may be redundant and they could go neutral.
  3. Charts, as above.

One known factual item, unrelated to styling: the hero stat card says
"121 Dataflows", hardcoded, against 127 in the index.

gvdr and others added 24 commits August 7, 2026 08:15
A package-lock.json above this directory makes Turbopack's root inference
select /home/gvdr/reps, which breaks module resolution locally: the dev
server logs "Can't resolve 'tailwindcss'" and then serves every page
unstyled while still returning 200. Easy to misread as the page being
broken rather than the toolchain picking the wrong root.

Vercel is unaffected, since the repository is the root there, which is
why this only ever appears in local development.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The visual identity guidelines (52pp, V1.0) plus logo, pattern, icon and
font artefacts, ~300 MB from SharePoint. The source package is
gitignored: SharePoint is its home, and only four curated SVGs belong
here.

The headline is that we are most of the way there already. The PDH
website uses Inter (p26, p27) and so do we. Trade Gothic, which the
guidelines otherwise specify, is for the logotype and print, and is a
licensed Linotype face we could not have served as a webfont. What looked
like the largest obstacle before reading the document does not exist.

What remains is colour, chrome and iconography: dark blue #223b83,
turquoise #00a6c8, deep orange #e37b0a as an accent only, a turquoise to
dark blue gradient, a solid dark blue header with the active nav item in
orange, a near-black footer, and the horizontal logo lockup, which is the
one case where the guidelines prefer it over the centred version.

Two things recorded rather than resolved. The supplied artwork renders
#1c478e and #e47f25 against the #223b83 and #e37b0a printed on p11,
likely a CMYK to RGB export difference; the guidelines are taken as
authoritative and the artwork left untouched, since altering brand files
is PDH's call. And the fifteen domain pictograms map onto the categories
we already carry per dataflow, which is an alignment opportunity that
costs little because the categorisation exists.

The logos are converted to SVG from the vector PDFs rather than shipped
as PNG, so the header scales without raster assets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The Web Elements folder arrived on a second attempt and supersedes the
vector-PDF conversions: web-ready SVGs for the logos, the fifteen domain
pictograms, the pattern at three densities, and a favicon set.

It also resolves what the last commit left open. The print artwork
renders #1c478e and #e47f25, which looked like a conflict with the
#223b83 and #e37b0a printed on p11. The official web SVGs use the p11
values exactly, so the guidelines are authoritative for web and the print
files simply carry a CMYK to RGB export difference. Nothing to raise with
PDH.

Recorded because someone will otherwise fix them: the pictograms use
#f47216 and the pattern #1ab4cf, both brighter than their brand
equivalents. They sit on dark blue fields where the brand values would go
muddy, so they read as intended rather than as drift.

The favicon set is not adopted. It is the PDH mark, and taking it would
have Surfer's browser tab claim to be Pacific Data Hub rather than a tool
within it. That is the same identity question as the per-tool header on
p28, and it is a decision rather than a file copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Sets the name in Trade Gothic Bold Extended, the face the guidelines
specify for the logotype, with two bespoke glyphs in the PDH manner.

The lambda is the A with its crossbar removed, which is what PDH already
does. Its geometry is lifted from the real A outline rather than
eyeballed: the outer edges are the A's own, the inner edges are those
offset by the stem width, and they meet at (.447,.5846).

The florin is the new one. The F keeps its uppercase skeleton so the cap
line stays unbroken, and the stem carries past the baseline into a wave
breaking left, drawn as a constant-width stroke. A literal florin is a
lowercase italic and would fight the geometry around it.

Two variants, because the choice is a judgement rather than a fact. With
one accent it matches the sibling tools, which use one each: .STAT
EXPL⊙RER, PACIFIC MΛP, NEXUS GE⊙NODE. With three it echoes the parent
lockup, but the lambdas only restate what PΛCIFIC DΛTΛ HUB already says,
so it reads as family resemblance where the florin reads as identity.

Glyphs are outlined into SVG rather than shipped as a webfont, which is
also the licence-clean way round: Trade Gothic is a licensed Linotype
face and outlining for a logo is ordinary desktop use, serving the TTF
would not be.

The preview route puts the pieces together: the p28 header with the
pattern as a faint watermark, both wordmarks, the palette including the
two accents that differ from brand, and the fifteen pictograms. Not
linked from anywhere; deleting the route removes the experiment.

Also excludes brand/ from the proxy matcher. Public assets were being
auth-gated, so the logo on the sign-in page, which is itself public,
would have been blocked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
One accent, per the decision: DATA SURƒER. The florin is Surfer's own
mark and carries the meaning, where a lambda in DATA would only restate
what PΛCIFIC DΛTΛ HUB already says. It also matches the sibling tools,
which use one accent each. The three-accent variant is retired.

The wordmark was generated on a 1240x320 page of which 74% was empty
margin, so sizing the image by its box made the product name look far
smaller than the parent lockup beside it. Now cropped to its ink,
941x119. Note that cap height is only 68% of that box, the rest being
the florin's tail, so box heights are not interchangeable when aligning
it and it needs a nudge up rather than true centring.

BrandField carries the pattern across header, hero and footer rather
than leaving it a header watermark. Drawn with a CSS mask, so one 186 KB
asset takes whatever colour a surface needs instead of shipping a
recoloured copy per context; where masks are unsupported the field is
flat colour, which degrades cleanly rather than breaking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Tokens first. The three anchors become PDH's: primary #223b83, secondary
#00a6c8, tertiary #e37b0a. Derived tints are recomputed by carrying the
lightness and saturation offset each had from its anchor, so the ramp
keeps its shape rather than being re-picked by eye; two of them overshot,
because the old anchors were far more saturated, and were set by role
instead. brand-theme.ts mirrors the same values for the dashboard theme
and export, which cannot read CSS variables.

Display type moves from Montserrat to Inter, because that is what the PDH
website uses. One less font request, and one less thing to be
inconsistent about.

Chrome: AppHeader is the p28 tool bar, the same one .STAT EXPL⊙RER and
PACIFIC MΛP wear, with our lockup and wordmark. AppFooter becomes the p27
near-black field, keeping its old single line behind a compact flag.
Neither is mounted globally: pages compose their own top areas today, so
adopting these is per-page rather than a change that lands everywhere at
once.

Pictograms are mapped to the categories the index already carries, which
is why this is cheap: both halves existed. Five of ten map. The rest
return null on purpose. XDO (Multi-domain) and IND (Industry and
Services) have no pictogram that means them, and SDG, BP50 and NMDI are
indicator frameworks rather than subjects, so an icon would assert a
domain the dataflow does not have. A wrong domain label is worse than
none, and the tests pin that rather than leaving it to be improved
later.

The pattern gets a rule instead of case-by-case judgement: density runs
inversely to information density. Strong where there is no data, absent
behind data. The empty state is the interesting case, since it inverts
the instinct: the user with nothing to read is the one with the most to
be persuaded by.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The wordmark sat high in the banner, and my earlier nudge made it worse:
I moved it up when the descender means it needed to go down. The florin's
tail occupies the bottom third of the image, so centring the bounding box
leaves the caps visibly above centre.

Fixed in the asset rather than in CSS. The viewBox is now padded so its
centre IS the cap centre, which makes items-center correct everywhere and
removes the per-call-site nudge entirely. Caps are 49% of the image
height, so the header uses a larger h to compensate. This is the kind of
alignment that gets re-broken every time someone reuses the asset, so it
belongs in the file, not in a class name.

The home page now wears the PDH header. AppHeader gained an actions slot,
so the settings, admin and sign-out controls move onto the bar restyled
for a dark field instead of being replaced. The old glass panel and the
local SurferLogo branding block are gone.

The hero was filling its space with blurred circles. That is generic
decoration, and the Pacific pattern is the identity's own answer to a
large empty field, so BrandField's hero variant replaces it: full
strength, because nothing on that panel competes with it. This is the
rule from the last commit applied rather than described.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The home page headline and body copy were sitting on full-bleed pattern
and the contrast was too low to read comfortably.

The rule I wrote was the cause. "Pattern density runs inversely to
information density" permitted a strong pattern wherever there was no
data, and the hero has no data, so it got the pattern behind its text.
No data is not no text.

Replaced with: the pattern goes around content, never under it. That is
also what the guidelines do, on every section divider the pattern sits
top-right and bottom-right with the title in clear space, and it is the
one instruction they give outright: always give priority to legibility.

BrandField now takes a patternArea, so this is enforced rather than
remembered. The hero uses a frame: clear in the middle, present in the
outer band. Not a right-hand band, which was the obvious choice and is
wrong here, because both hero columns carry text and below lg the text
column goes full width, so a vertical band would end up under copy at
some breakpoint. A frame sits in the padding at every size without the
component knowing the layout. The header keeps the full width as the
deliberate exception: a thin ribbon with large type, which PDH's own
headers do the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The flat grey behind the cards was the largest empty area left, and it
takes the pattern without breaking the rule: every card, panel and chart
is opaque and sits on top, so the pattern only shows in the gutters and
nothing is read through it.

In the brand dark blue at 5.5%, baked into a separate asset rather than
applied with a mask. Masking the body background needs a stacking context
on <body>, which is a lot of risk to take for a texture, whereas a plain
background-image cannot interact with anything. The cost is one more
187 KB file, cached and requested once.

Fixed attachment, so the field stays put while content scrolls, which
reads calmer than a moving one; switched to scroll under
prefers-reduced-motion, where a fixed field can shimmer against moving
content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
At 5.5% the page background was effectively invisible, which is not what
was asked for: the pattern should be seen in the areas around the cards,
like the band behind Quick Start.

Raised to 12% and the tile tightened from 46rem to 34rem so it reads as
pattern rather than as a faint wash.

Checked rather than eyeballed, since text does sit on this surface. On
the darkest point of a pattern line the background blends to #e0e4ee,
where the section headings hold 8.0:1 and body text 12.9:1. Both clear
AAA, and they would still clear it at 16%. That headroom exists because
every card is opaque, so the only text over the pattern is large
headings on a near-white field; it is not a licence to put the pattern
behind anything else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The florin is now the font's glyph, U+0192, not a drawn approximation.
It was worth checking for: the previous version was an F with a tail
that I had to weight and curve by eye against a face somebody else
designed, and the real one is simply better.

Finding it took a detour. pymupdf drops the character silently because
insert_text encodes Latin-1, and its TextWriter cannot embed this font
at all, so it looked absent. Reading the cmap directly showed U+0192
mapped to glyph 100 all along. The wordmark is now set from the font
outlines via fontTools, with kerning, which also fixes spacing the
previous approach got wrong: the florin advances 0.722em against the F's
0.778em, so patching one glyph into a layout built for the F left a gap.
Files dropped from 8 KB to 4 KB as a side effect.

The headline reads Surƒ the Data, using Inter's own U+0192 rather than
the SVG. That answers the question behind it: the two are deliberately
different faces. Trade Gothic is the logotype and print face and cannot
be served as a webfont, Inter is the website face (p26, p27). A logo in
one and running text in the other is the guidelines' own split, not an
inconsistency to fix.

Checking contrast for the accent turned up a live problem in the hero
that predates this. The gradient ran turquoise into dark blue, and the
headline and paragraph sit in the top-left, on the turquoise end, where
white is 2.9:1. That is under AA for body text. The gradient now starts
dark where the copy is and runs turquoise out to the right behind the
stat cards, which carry their own surfaces: white goes to 10.4:1. The
guidelines put their titles on the dark end too.

The accent needed its own value for the same reason. #e37b0a is 3.5:1 on
the dark blue and 2.9:1 over the gradient's mid tone, under AA even for
large text, so --color-accent-on-dark holds 4.6:1 and 3.7:1. Chrome and
light surfaces keep the brand value.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Checked rather than assumed. PDH's guidelines say it twice, p26 and p27:
the website uses the Inter font family. The live site agrees, serving
Inter, Roboto Flex and an icon font, with no occurrence of Trade Gothic
in the page or any stylesheet. So the split is PDH's own position, not a
constraint we invented.

Recorded alongside it: desktop and webfont licences are sold separately,
so having paid for Trade Gothic can be true and still not cover
@font-face. If a webfont licence covering the domain is confirmed later,
serving it is about half an hour and the wordmark could become live text
rather than SVG; the doc says what that would take and why it is still
worth thinking twice, since Trade Gothic Bold Extended is a display face
and most of our screens are dense UI and data labels.

Also fixes a weight that was never loaded. The hero stat figures use
font-black, which is 900, while the font request stops at 800, so the
browser has been synthesising it. The old Montserrat request stopped at
800 too, so this predates the switch. Moved to font-extrabold, which is
a weight we actually load, rather than adding a sixth file to the
critical path for two numbers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The headline and the wordmark carried different florins: Inter's in the
running text, Trade Gothic's in the mark. Same character, two drawings,
in the same view.

My reasoning for that was wrong. I justified it as the guidelines' own
split, Trade Gothic for the logotype and Inter for the website, which is
right for running text and does not apply here. In the headline the
florin is not a letter, it is the mark being quoted inside a sentence.
Rendering it in a different face is the one thing that stops it reading
as the same identity.

So the headline now uses the wordmark's glyph, as an inline SVG. Inline
rather than an <img> so it inherits currentColor and costs no request.
It is placed from the font's own metrics rather than nudged: the glyph
spans 929 units of a 1000 unit em and descends 184 below the baseline, so
it is 0.929em tall and shifted down 0.184em, which puts its baseline on
the text baseline instead of its bottom edge where a replaced element
would otherwise sit. Rendered both together to confirm they match.

It keeps role=img with an "f" label, so the line still reads as "Surf the Data" to a screen reader.
These are the other eleven sites of the change committed with the page
background: every page root carried min-h-screen bg-surface, and
bg-surface is an opaque background-color covering the full viewport, so
it painted straight over the body's pattern. Only app/page.tsx made it
into that commit; the rest were staged by path and missed.

The colour is redundant in all of them, since <body> already carries it.
The pattern was being requested and rendered the whole time and then
hidden by an opaque rectangle, which is why it read as flat grey on every
page rather than only some.

Also ignores the two dev logs this worktree writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The token swap changed the main app and left this module behind, because
its colours were written as Tailwind arbitrary values rather than tokens.
So Country Snapshots was still Oceanic teal while everything around it
had gone PDH blue. That is the module built for MFAT, and the one most
likely to be shown to a partner.

49 arbitrary values across 16 files now use tokens. The mapping was
chosen against measured usage rather than by matching hues, and one
choice matters: #006970 is TEXT in seven of its eight sites, so it maps
to the dark cyan rather than the brand cyan. Mapping it by appearance to
--color-secondary would have put 2.9:1 text on every one of them; the
dark cyan holds 7.3:1. Both the navy and teal buttons carry white text,
so their replacements had to stay dark for the same reason.

--color-primary-dark is new, for the darkening hover on those buttons.
The existing container token is lighter than primary, so it cannot serve
that role.

The structure diagram is the one place that cannot use tokens, since
Mermaid reads a JS object and not CSS. It now takes its values from
BRAND_THEME rather than repeating literals, so it follows the palette
instead of drifting: the previous theme mixed Oceanic blues with Material
green and orange and matched neither of them. BRAND_THEME gained the
accent values it needed for that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The pictogram mapping existed and nothing called it. Now the catalogue
cards and the dataflow page both show the Pacific Data Hub icon for their
domain, larger on the detail page since it carries one dataflow rather
than a grid. It is absent more often than present, by design: five of the
ten categories have a pictogram that honestly means them, and the rest
return null rather than borrow a near-miss.

The category badges keep their colours. They are a rainbow of Tailwind
hues and off-brand, but they are also doing categorical work that three
brand colours cannot do, which is the chart problem in miniature. Worth
deciding while looking at it rather than now: the pictogram may make the
colour redundant, in which case the badges could go neutral.

Sign-in becomes a split field, brand on the left and the form on a plain
surface at the right. It is the strongest brand moment in the app and was
a small card on grey, and it is the first thing every new user sees. The
pattern runs full strength there because nothing is read over it, and the
form never sits on pattern at any width, which keeps the rule intact
rather than making an exception for the page that most invites one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
An audit that resolves Tailwind classes against the real token values and
checks every text/background pair that co-occurs on an element. It kept
as scripts/audit-contrast.py, because this is the kind of thing that
regresses quietly.

Four failures, all of which had shipped:

  1.1:1  a Measure badge that was light orange on light orange
  2.4:1  search placeholders at 50% opacity
  2.9:1  the brand cyan as text on white, 18 occurrences
  4.2:1  a muted grey that passes on white and fails on the surface it
         is actually used on

The cyan is the same trap that came up in Country Snapshots: #00a6c8
reads as an obvious choice for accent text and is not one. The grey is
the more interesting failure, because checking it against white would
have said it was fine.

Focus had no floor at all. Nothing in the app defined a focus-visible
style and six components apply outline-none, so some controls had no
visible keyboard indicator. There is now a ring on anything focusable,
keyboard-only so it never fires on click. Components can still style
focus; they cannot remove it.

Reduced motion had one rule covering the page background against 26
animate-* utilities. Someone who asks for reduced motion now gets the end
state rather than the movement.

Two findings in the first pass were the audit's own bugs rather than the
app's, and are recorded in the script instead of being "fixed": an <img>
matched inside a doc comment, and every input looked unlabelled because
an arrow function in a JSX attribute ends a naive tag match at its >.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The left panel runs the pattern at full strength and the text sat
directly on it. Measured against the worst case, where a line of the
pattern passes behind the copy, the tagline was 2.37:1. That is the
"pattern goes around content, never under it" rule broken by me, on the
page that most invites the exception, three commits after writing the
rule down.

A translucent card at black/35 brings it to 5.7:1 while staying light
enough that the pattern still reads through it. Dark rather than white,
so the white wordmark sitting above keeps its own contrast.

The audit script did not catch this: the field is a gradient with a
masked overlay, neither of which is a Tailwind colour class, so it never
appeared as a text/background pair. Worth remembering that a clean run
means the pairs it can see are fine, not that the page is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Two renames with different rules, applied together because leaving
either half would have produced exactly the inconsistency the pass was
meant to remove.

The product is Data Surfer now, so 30 user-facing "SDMX Surfer" strings
follow: the page title, the builder header, the chat panel's empty
state, the gallery copy, invite and access emails, the usage-budget
message, and the footers stamped into exported dashboards and PDFs.

The standard has been styled SDMx since the SIS-CC restyling, so 42
prose occurrences follow that too.

Identifiers keep their old spelling, and the pattern gets that for free
rather than by an exception list: it matches SDMX only when the next
character cannot continue an identifier. So SDMXDashboard, SDMXParser
and SDMXVisualConfig are untouched, and sdmx-dashboard-components and
SDMX_ENDPOINT differ in case or separator anyway. Verified afterwards:
11 files still reference SDMXDashboard, tsc is clean, 249 tests pass.

One consequence worth stating rather than discovering: this edits both
system prompts, so the Anthropic prompt cache misses once per prompt on
first use after deploy. A single cold turn each, not a behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
The working screen stays quiet, which is right: the transcript is
long-form reading and the preview is data, so neither takes pattern. But
quiet had become anonymous, and it was the one screen that did not look
like part of the product.

Two changes, both cheap in the currency that screen is short of.

The header carries the real marks instead of a coloured square and two
lines of text: the PDH lockup, a divider, the Data Surfer wordmark, in
their coloured versions since that bar is a light glass panel rather than
the dark PDH header. Recognisable, and no taller than what it replaced.

The chat empty state gets the pattern. That is not an exception to the
rule, it is the rule's clearest case: nothing is being read, the user is
waiting rather than working, and it disappears the moment a conversation
starts. BrandField gains a panel variant for it, on a light surface with
the pattern in brand blue at 10% and framed so the copy stays clear, so
other empty states can reuse it rather than each inventing something.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
There was no not-found.tsx, so Next served its own default: black
Helvetica on white, no navigation, nothing naming the service. A 404 is
reached by someone who followed a stale link or mistyped, which makes it
one of the few pages guaranteed to find a person already slightly lost,
and the worst place to hand them a dead end. It now carries the wordmark,
says what probably happened, and offers three ways onward.

Verified on the path that matters: a signed-out visitor following a dead
link to a published dashboard, /p/<gone>, gets the branded page. Unknown
routes elsewhere still redirect to sign-in, which is correct for a
private app and not something the 404 should override.

The eight surfaces are the same bug as before, missed because the first
sweep matched "min-h-screen bg-surface" as adjacent words and these read
"flex min-h-screen items-center justify-center bg-surface". Mostly
loading and error states, plus the Country Snapshots layout and the
builder. Matching on the class list rather than the literal string found
them; there are now none left outside <body>, which keeps the colour
because it is the surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
It is a sibling of Data Surfer under the Pacific Data Hub: separate
audience, separate access, its own name. So it gets its own wordmark and
the same PDH bar, the way .STAT EXPL⊙RER and PACIFIC MΛP do, rather than
wearing Data Surfer's marks.

C⊙UNTRY SNAPSHOTS, with the circled O. That accent rather than a lambda
in SNΛPSHOTS: both were built and rendered, and the lambda mid-way
through a long word reads like a typo at a glance, where the circled O
at the second letter reads as deliberate. Trade Gothic has no U+2299, so
the glyph is the font's own O with a dot at its optical centre, sized at
12.5% of the O width to leave the counter breathing; the first attempt
at 15.5% looked stopped-up.

The two wordmark generators are now one script covering three kinds of
accent: a real glyph substituted from the font, which is what the florin
is and what to reach for first; the A with its crossbar removed; and the
circled O. Adding a third tool is a table entry.

AppHeader takes the product rather than being forked, so the bar cannot
drift between the two. Country Snapshots is set smaller in it: the same
cap height on a much longer word would leave the bar as nothing but
wordmark. Its logo links to its own home, not to Data Surfer's, which is
the part that makes it read as a sibling rather than a section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Giving Country Snapshots the shared footer left it saying "Data Surfer
· Built at the Pacific Community" on every one of its pages, which
undoes in the footer what the header just established. AppFooter takes
the same product prop the header does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sdmx-surfer Ready Ready Preview Aug 13, 2026 5:26am

Request Review

The UI was renamed on this branch; the docs still said SDMX Surfer, and the
README and technical reference still carried the older descriptive name,
"SPC Conversational Dashboard Builder". Three names for one product across
one repo.

Also applies the SDMx restyle to doc prose, by the same rule the UI uses:
the standard becomes SDMx, and identifiers keep the old spelling, since they
are names rather than prose. SDMXDashboard and its config types belong to
sdmx-dashboard-components, SDMX_ENDPOINT and SDMX_STATSNZ_KEY are environment
variables, and SDMX-JSON is a format name published under the old styling.

Dated records are left alone: stitch_assets, docs/superpowers plans and specs,
and dashboard-architecture.md, which labels itself Version 0.2, March 2026.
Those say what was written at the time, and a rename would quietly rewrite it.
Say the word if uniformity is worth more than that.

The slide deck is renamed on disk and its four affected text runs edited
inside the package, rather than regenerated. The committed .pptx is a hand
edit from 2026-07-27 that build_pptx.py cannot reproduce: it carries two
embedded screenshots and later copy on slides 3 and 4, and rebuilding it
drops the file from 977 KB to 37 KB. The script now says so at the top,
because nothing else in the repo did.

The licence files never named the product, so nothing changed there. They
name SPC as copyright holder, which is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
It opened with "No code has been implemented yet" and described a repo of
scoping documents and UI mockups. That has been wrong for months: this is
the deployed app. It also still described the Oceanic Data-Scapes theme,
which this branch replaced, so a session reading it would have been sent
at the palette we just migrated away from.

The rewrite points at docs/current-architecture.md rather than restating
it, since duplicating 383 lines of route and access semantics into a file
nobody rebuilds is how this went stale in the first place. What stays here
is what is not derivable from the code: the MCP tool contract, the design
tokens, and which documents are current against which are dated records.

Records the Country Snapshots gap rather than papering over it:
current-architecture.md does not cover it at all.

The MCP tools section is unchanged apart from its punctuation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JCDoYnDMGZmu3jREoEKhMo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant