Skip to content

release: dev to prod (UX Core audit fixes, Umami, SEO fixes) - #184

Merged
manager merged 16 commits into
mainfrom
dev
Aug 19, 2026
Merged

release: dev to prod (UX Core audit fixes, Umami, SEO fixes)#184
manager merged 16 commits into
mainfrom
dev

Conversation

@manager

@manager manager commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Promotes dev to production.

Includes since the last release:

  • Top-10 UX Core functional audit fixes (fix: UX Core functional audit fixes + OffSec bias cases #181) + review follow-up (fix(uxcore): address PR #181 review comments #183): UXCAT answer delivery confirmed before advancing, server-trusted final-test resume, rating vote integrity, Strapi bias-cache TTL (no more 404-poisoning, content edits reach prod), fixed per-bias share previews and encoded share links, Back/Forward bias-modal sync, search zero-hit state and count labels, truthful persona save with per-call auth, persisted PM/HR view choice.
  • OffSec layer stays fully gated to the dev preview: the switcher row, #offsec deep link and persisted flag are all inert on staging/prod.
  • Everything already on dev since the last prod release (self-hosted Umami tracker + CSP, SEO/canonical fixes, workflow gating).

🤖 Generated with Claude Code

manager and others added 16 commits July 16, 2026 20:40
…ter, timeline)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ail surfaces

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The connector was anchored to both circles with left/right, but also
carried a fixed `width: 79.5%` that won over the right anchor — so the
line stopped short of step 2 at every viewport except the one the
percentage was eyeballed at.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The two fields stack in the same form but were never given the same
geometry, so they read as two unrelated controls: different heights and
only the dropdown had a divider before its chevron. The multi-select now
mirrors the dropdown trigger — 44px tall, 8px radius, and a chevron
wrapper that carries the divider and recolors with the border on hover.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The placeholder row was pinned to 50px, which with the wrapper's 12px
padding made a 74px box holding a single line of text. Pinning the row
to its 20px line box brings the container to 44px.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every hit box had drifted off its artwork — up to 4% horizontally and
14% vertically — so only the rooflines answered the pointer. Each box is
now measured against the silhouette its glow SVG actually draws, for the
wide and panorama art independently.

house-2 is the tree behind the buildings rather than a building of its
own, so its box necessarily overlaps John's pagoda. Overlapping triggers
share a z-index and the last declaration wins the pointer, so the tree
moves to the front of the array and the buildings keep their own areas.

Verified by hovering a 5x5 grid inside each building at 1024/1440/1920/
2560px: every building resolves to its own card, and the samples where
the tree loses are the ones John's pagoda sits in front of.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
fix(library): four polish fixes to the library UI
The Cybersecurity row and the #offsec hash handler in UXCoreLayout were
ungated, so merging would have leaked the dev-only OffSec layer to prod
in a half-broken state (row active, modal showing HR). The gate is back
as a single shared isOffsecEnabled helper used by layout and modal.

initUseUXCoreGlobals had zero callers: every localStorage write of the
PM/HR choice was dead weight and each reload reset to PM. It is now
idempotent (absolute values, safe on every mount), gates the OffSec flag
on isOffsecEnabled, and runs on /uxcore mount with an explicit URL hash
winning over the restored state.

Also adds the machine-readable font passport to CLAUDE.md (commit gate
requirement).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… cache TTL

- Back/Forward now lands on the right bias: modal state follows the route
  prop instead of freezing at mount, so the stuck-dimmed modal is gone.
- Use-case switch inside the modal is a shallow no-scroll hash update
  (was a full ISR refetch + jump to top), and OffSec state writes #offsec
  instead of mislabeling the URL as #hr.
- Share previews: og:image/twitter:image static fallback is absolute
  (scrapers drop relative URLs), the Strapi ogImage path lost its bogus
  double .OGTags nesting, twitter:url is the canonical URL (was a
  hardcoded domain with a double slash), and social share links are
  URL-encoded so hashes/ampersands in titles survive.
- getStrapiBiases: 5-minute TTL instead of a permanent module cache, and
  an empty/incomplete response is never cached — a transient Strapi
  failure no longer 404s all bias pages until restart, and ISR
  revalidation actually picks up content edits.
- Rating: thanks-state only after the vote lands (failed vote stays
  retryable), double-click guard, response.ok check, and a failed geo
  lookup no longer aborts the vote.
- mentionedQuestionsIds parse failure degrades to no mentions instead of
  crashing the bias page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- A search with zero hits now renders as empty (labels faded, map
  filtered to nothing) instead of silently falling back to the full
  library while claiming 'found 0'. useBiasSearch carries an explicit
  isSearchActive flag so empty-query and zero-hit are distinguishable.
- Clearing the input cancels the in-flight debounce, so a pending search
  can no longer repopulate results over a cleared field.
- getSearchLabels indexed a locale-keyed object numerically and always
  returned undefined: EN plural 's' and RU declensions never rendered.
  It now takes the locale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- addPersona/updatePersona/deletePersona built auth headers at module
  load, so logging in after page load kept sending 'Bearer null'.
  Headers are now resolved per request, and non-2xx responses throw.
- Save/overwrite no longer flip to 'saved' optimistically: the state is
  confirmed only after the request lands, and a failure surfaces the new
  localized saveFailed message via the existing error slot.
- A failed delete keeps the persona in the list instead of hiding an
  entry the server still has.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… server

- A dropped submit no longer advances the test: the answer is confirmed
  by the server before moving on. On failure the question stays with a
  localized retry message and the button re-enables, so a network blip
  means a retry instead of a wrong score or a test finished with
  unrecorded answers.
- Double submits closed: an in-flight ref guards clicks, and Enter now
  mirrors the Next button's disabled state instead of firing the same
  question twice.
- Resuming a test uses the server's ongoingTest.isFinal instead of a
  localStorage flag, so a 30-question final resumed in another browser
  (or after cleared storage) keeps its real length instead of ending at
  question 10 with negative numbering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	src/uxcore/api/biases.ts
#	src/uxcore/components/UXCoreModal/UXCoreModal.tsx
#	src/uxcore/hooks/useUXCoreGlobals.ts
#	src/uxcore/layouts/UXCoreLayout/UXCoreLayout.tsx
#	src/uxcore/lib/offsec.ts
fix: UX Core functional audit fixes + OffSec bias cases
- OffsecBiasView: conditional classNames via the cn() helper instead of
  raw template strings, per AGENTS.md convention.
- rating.ts: geo payload typed as TUserGeo instead of an explicit any.
- OngoingLayout.module.scss: submit-error color reuses the shared error
  red as a named variable instead of a fresh hardcoded literal.

Fourth review note (OffSec bias-schema sign-off) needs no code change:
the content and its dev-only gating were explicitly approved by Wolf.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Claude finished @manager's task in 3m 35s —— View job


Review of PR #184 (dev → main release)

Todo list

  • Gather context (read diff vs origin/main, read CLAUDE.md/AGENTS.md)
  • Review React/hooks correctness & SSR safety
  • Review TypeScript quality
  • Review project convention compliance (AGENTS.md)
  • Review accessibility & UX
  • Review security (XSS, secrets, URL handling)
  • Review styling (SCSS hygiene, design tokens)
  • Flag any UX Core bias data/schema changes for explicit approval
  • Post inline comments for specific issues
  • Post final summary

Summary

This is a release PR bundling commits already reviewed individually in #181 and its follow-up #183, plus the earlier Umami/CSP/SEO/library-polish work already on dev. I diffed against origin/main directly and re-read the substantive hunks rather than re-litigating what #181/#183 already covered. No blocking issues found; a couple of minor, non-blocking observations below (no inline comments needed — nothing rises to actionable-fix level).

What's solid:

  • src/uxcore/api/biases.ts — the TTL cache with an isComplete guard is a real fix for the described 404-poisoning bug: a partial/failed Strapi response no longer overwrites a good cached copy, and a genuinely complete fetch now expires and refreshes so CMS edits reach prod.
  • src/uxcore/api/personas.ts / rating.ts — moving the auth header from a module-level snapshot (frozen at first load) to a per-call getAuthHeaders() closes a real "stuck logged out" bug; response .ok checks now correctly propagate failures instead of resolving with a parsed error body.
  • src/uxcore/layouts/OngoingLayout/OngoingLayout.tsxsubmittedAnswer now returns a boolean tied to whether the request actually landed (getUXCatSubmitTest returns null on failure per src/uxcore/api/uxcat/submit-test.ts), and the test only advances on success; a isSubmittingRef ref (not state) correctly guards against double-submit races that state wouldn't catch before the next render.
  • src/uxcore/lib/offsec.ts — gating flips from "hidden only in prod" to "enabled only in dev", matching the PR description that OffSec must be dark on staging too. initUseUXCoreGlobals in useUXCoreGlobals.ts correctly forces isOffsecView to false whenever isOffsecEnabled is false, regardless of what's in localStorage, so a flag persisted on the dev preview can't resurface the layer on a public build.
  • src/pages/uxcore/[slug].tsx — the mentionedQuestionsIds JSON.parse is now wrapped in try/catch with an array-type check, closing a crash-on-bad-Strapi-data path; the Back/Forward modal-sync effect now depends on currentActiveBias.number instead of running mount-only.
  • Import ordering throughout the touched files matches the documented ESLint sort order (side-effect → node → third-party → local aliases → relative → styles).
  • classnames/cn used consistently instead of raw template-string class concatenation (e.g. OffsecBiasView.tsx).
  • No 'use client', no App Router imports, no Tailwind/CSS-in-JS, no new state libraries, no global CSS imported outside _app.tsx, no <img> for SVGs, no empty barrels introduced.
  • src/uxcore/data/biasOffsec/* — the 10 new OffSec case files are internal "attack simulation" demo content (not UX Core's canonical bias library), and the whole layer stays gated behind isOffsecEnabled (dev-only) per offsec.ts. This doesn't touch UX Core bias names/slugs/citations, so it doesn't fall under the "explicit approval" bar in CLAUDE.md — flagging here only for visibility, not blocking.

Minor, non-blocking observations:

  • src/uxcore/data/decisionTable/hy.ts adds saveFailed with the same English string as en.ts, rather than a real Armenian translation or omitting the key (AGENTS.md notes hy.ts should only be provided "if you have real Armenian translations"; since this file already exists and is used directly for the hy locale, the English string will render as-is rather than triggering any fallback). Cosmetic — same pattern likely exists already elsewhere in this file.
  • src/uxcore/layouts/OngoingLayout/OngoingLayout.module.scss adds $error-red: #fb1717 — confirmed this reuses the existing hardcoded color from DecisionTable.module.scss rather than inventing a new value, so it's consistent with (if not exactly compliant with) current styling conventions.

No security, SSR/hydration, or hooks-dependency issues found in the reviewed hunks.

@manager
manager merged commit f57af72 into main Aug 19, 2026
3 checks passed
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.

2 participants