builds on per-doc routing: long docs (API reference has 6 sections, Roadmap 4)
are now navigable and section-shareable.
- each h2/h3 gets a slug id + a hover '#' anchor; URL form is
#/<doc-slug>/<section-slug> (e.g. #/api-reference/keys-self-service)
- an 'On this page' TOC renders for docs with >=3 headings; click or deep-link
scrolls to the section; browser back/forward still work
- decorations (anchors, ids, code copy buttons, syntax highlighting) are now
baked into the memoized HTML string instead of mutated in post-render DOM
WHY: the post-render mutation approach was being wiped. setToc() inside the
decorate effect triggered a re-render that re-applied dangerouslySetInnerHTML,
resetting the article's children and dropping every decoration, with the
effect never re-running (deps unchanged). Doing it at the string level makes
the markup self-contained and immune to re-injection. Copy buttons use event
delegation; anchors are real links (work without JS).
- verified live: 6/6 heading ids + anchors on API reference, TOC navigates,
3 copy buttons + highlighting on Connect-your-broker, deep-links land and
scroll to the section, TOC hidden on short docs
the docs site kept the selected document only in React state, so you couldn't
bookmark, share, or refresh-into a specific doc, and browser back/forward did
nothing — a real gap for a developer docs site people want to link into.
- each doc now has a slug URL: docs.amerc.ai/#/connect-your-broker
- selecting a doc pushes the slug to the URL (history.pushState)
- loading a #/<slug> URL lands directly on that doc; unknown/empty falls back
to Quickstart as before
- hashchange + popstate listeners make browser back/forward switch docs
- the browser tab title now reflects the open doc (<title> · Docs · amerc)
- verified live on docs.amerc.ai: click updates URL+view+title, deep-links
land correctly, back/forward navigate between docs
sharing amerc.ai in Slack/Discord/X/etc. showed a raw pixel-art tavern
screenshot — distinctive but cluttered at thumbnail size and missing the
wordmark and tagline. replaced it with a designed 1200x630 OG card:
gem + 'amerc' wordmark, the 'Hire agents like mercenaries.' tagline, a
one-line description, amerc.ai, and the three delivery modes — on the
brand's dark/cyan/gold palette over a deeply dimmed tavern backdrop.
- rendered headless at 1200x630 (Page.captureScreenshot, jpeg q88, 58KB)
- point og:image + twitter:image at /og-card.jpg, fix width/height to 1200x630
- verified live: meta tags resolve, image serves 200 image/jpeg
the <div id=root> was empty until the JS bundle downloaded and React mounted,
so the page was blank for ~2.4s (FCP). put the branded loading screen (pulsing
gem + 'amerc') directly in the static HTML, with its critical CSS inlined in
<head>, so it paints as soon as the CSS loads instead of waiting for JS.
- React's createRoot replaces #root on mount; the Suspense fallback renders the
identical markup, so the static->React handoff is seamless (no flicker)
- inlined .app-loading critical CSS mirrors styles.css exactly + dark body bg
so there's no white flash during load
- verified live: FCP 2.4s -> 1.4s, performance 86 -> 90, TBT 50 -> 0ms;
app mounts cleanly, static loader replaced, 0 console errors
measured the LIVE site with Lighthouse (best-practices 100, seo 100,
agentic-browsing 100; a11y 98, perf 86) and fixed two real flags:
- heading-order: the footer column labels were <h5> following an <h3>
(skipping h4) — changed to <h4> so the page heading order is sequential;
a11y 98 -> 100
- caching: every static asset was served with no cache lifetime (0s).
added nginx cache locations on the amerc.ai vhost (server-side, not in repo):
/assets/ -> 1y immutable (content-hashed chunks), /scene2d/ -> 30d;
index.html stays uncached so it always points at the latest assets.
repeat visitors now serve JS/CSS/images from cache. cache-insight -> PASS
- verified live: a11y 100, heading-order + cache-insight PASS, cache headers
present on assets and absent on index.html
- amerc is genuinely agent-native now (JSON API + self-service keys + WebMCP
browser tools + llms.txt) but the home page never said so
- add a concise callout after the session preview: 'Agent-native by design —
mint an API key and drive it over JSON, get callable WebMCP tools on the
page, read llms.txt for the map. Agents can hire agents.'
- a real, differentiating positioning point, not filler; cyan-accented card,
scroll-reveal, links to /llms.txt, stacks on mobile
- verified live: callout renders and reveals
- llms.txt: add a 'Browser tools (WebMCP)' section listing the four callable
tools (amerc_browse_agents / platform_stats / get_agent / open) so agents
reading the file know they can act on the tavern, not just read about it
- footer: add a 'For agents ↗' link to /llms.txt so humans can discover
amerc's agent-facing guide (keys, JSON API, WebMCP)
- verified live: llms.txt WebMCP section served, footer link in the bundle
amerc is the agent mercenary tavern — so expose it to AI agents browsing the
page via Web Model Context Protocol (navigator.modelContext), the W3C browser
tool API. An agent on amerc.ai can now act on tools instead of scraping the DOM:
- amerc_browse_agents (query/tag) — list the roster
- amerc_platform_stats — live classes/online/sessions
- amerc_get_agent (id) — full class details + instances
- amerc_open (section) — navigate the tavern
- each has a valid JSON-Schema inputSchema; read tools carry readOnlyHint
- registered only on the main domain, feature-detected ('modelContext' in
navigator) + try/catch, so it no-ops with zero risk where unsupported
- verified (polyfilled modelContext): all 4 tools register with valid schemas
and their execute() calls hit the live API correctly
refs: W3C webmcp proposal, webfuse WebMCP cheat sheet
ran full Lighthouse: perf 97, a11y 100, best-practices 92->96, SEO 100,
and the new agentic-browsing category 67->100. amerc is for agents, so this
category matters most — its only fail was a missing llms.txt.
- add /llms.txt (llmstxt.org format): an agent-facing index of amerc — the
class/instance/session model, the JSON API (base, auth, key endpoints),
links to docs + source, and a 'for agents' note. genuinely on-brand for
the agent mercenary tavern and useful for agents driving amerc via API
- add <meta charset=utf-8> as the first head element (was missing)
- re-ran Lighthouse: agentic-browsing 100, charset + llms-txt PASS
- verified live: /llms.txt 200 text/plain
ran Lighthouse (local preview): SEO 100, a11y 93->100, best-practices 92
(its one fail was console errors from the backend-less preview — a false
positive; live has 0 console errors). fixed the 3 real a11y flags:
- color-contrast: '.gm-scrollcue' was 4.33:1 (needs 4.5) — lightened the
'how it works' cue colour and dropped the opacity dimming
- label-in-name: the logo button's aria-label='amerc home' didn't contain its
visible text — removed it so the accessible name = visible text (+ title=Home)
- skip-link: the href='#content' had no target — gave the content host
id='content' + tabindex=-1 so it's a valid focusable target
- re-ran Lighthouse: accessibility 100, all three audits pass
- the Suspense fallback (shown on every cold load while the app chunk loads,
longer on slow/mobile connections) was a plain monospace 'Loading amerc…'
- replace with a branded screen: a pulsing cyan gem over 'amerc' in gold
Georgia on a dark radial background, matching the logo + error-boundary look
- respects prefers-reduced-motion
- also audited the public mobile surfaces (agent modal, status, changelog) —
all mobile-safe (modal is width:min(640px,96vw) + scroll)
- verified the loading screen renders (blocked the app chunk to capture it)
- on phones the docs kept the desktop sidebar+content row, squeezing the prose
into ~240px (sidebar ate the rest) — poor reading on a public docs surface
- stack on mobile (<=760px): sidebar becomes a compact scrollable doc-picker
on top (max 210px), content goes full-width below with comfortable padding
- verified live (390px): layout column, content full-width (390 vs old 240),
no horizontal overflow
- visiting docs.amerc.ai showed a blank 'Select or create a document' main
area — a poor first impression for the public docs/onboarding surface
- auto-select a sensible default on load (Quickstart -> a 'start here' doc ->
first doc) without overriding a user's later selection
- verified live: docs.amerc.ai lands on Quickstart, sidebar highlights it
- the agent ClassModal and the booth 'instance created' modal could only be
dismissed by clicking the backdrop or the X — no Escape, a standard
keyboard/UX expectation
- add a keydown(Escape) -> close to both (ClassModal listens while mounted;
the booth modal attaches the listener only while newInst is set)
- verified live: agent modal opens then closes on Escape
- lucide-react was a runtime dependency used only for the mobile menu's
hamburger + close icons in Scene2D.jsx
- replaced with two tiny inline SVG components (lucide's exact Menu/X paths),
removed lucide-react from package.json — aligns with the project's
minimal-dependency philosophy (zero-dep backend)
- build is clean with no lucide in the bundle; icons render identically
- (deploy note: re-deployed after a transient fail2ban block from this
session's rapid deploy cadence — site stayed healthy on 0.73 throughout)
- searching (or tag-filtering) with no matches wrongly showed the 'No agents
here yet — be the first' onboarding, implying the whole marketplace was
empty even when agents exist
- now distinguishes: a query/tag with no matches shows 'No mercenaries match
<term>' + a Clear search button; the be-the-first onboarding only shows when
the roster is genuinely empty (no query, no tag)
- verified live: 'zzznomatch' shows the no-match state, Clear restores results
- keyboard/screen-reader users had to tab through the whole topbar before
reaching page content (WCAG 2.4.1 Bypass Blocks gap)
- add a skip link as the first focusable element: hidden until focused, slides
in as a cyan pill, and on activation focuses the visible content container
(works with the persistent-home structure, and avoids the hash-route conflict
by focusing programmatically instead of via href)
- verified live: Tab focuses it, it becomes visible, activation focuses content
- the SPA had one static title, so every tavern route and the docs/pm
subdomains all read 'amerc — hire agents like mercenaries' in the tab
- tavern now sets a descriptive title per route (Browse Agents · amerc,
System status · amerc, etc.); docs/pm set 'Docs · amerc' / 'PM · amerc'
- clearer browser tabs, bookmarks and history
- verified live across home/agents/status + docs + pm
- the mobile hamburger menu was a cramped narrow corner dropdown with no
backdrop and no tap-to-close
- now a full-width panel (left+right anchored), comfortable full-width touch
targets, a dimming tap-to-close backdrop, rounded card + shadow, fade-in
- verified live (390px): backdrop present, menu ~374px wide, tap-to-close works
- the LPC scene images only started downloading after the Scene2D chunk loaded
and TavernGame mounted, extending the 'loading tavern…' flash
- preload them in main.jsx (gated to the main domain via App===Scene2DApp, so
docs/pm don't fetch them) — they now download in parallel with the JS chunk
- verified live: images requested at 1461ms vs Scene2D chunk at 1467ms (before,
in parallel), canvas renders correctly
- tavern-hero.png (1.75MB) was unused — the OG/social image is the .jpg
- removed leftover concept-art sprites (dwarf/elf/orc/dungeon, ~110KB)
- bartender.png was fetched by the canvas but never drawn (the bartender is
rendered with the soldier sprite); dropped it from the loader + deleted it
- kept bg.png (CSS), tavern-hero.jpg (og:image), the LPC tiles, and the
CC-BY-SA credits/license files
- scene2d 2.4MB -> 560KB, dist 2.9MB -> 1.1MB; canvas loads one fewer image
- verified live: tavern renders identically, 0 failed requests, og:image 200
- the booth and account logged-out gates were the last plain 'Log in' surfaces
(one line + a tiny pixel button). Reworked both into branded, inviting cards
with an icon, a real pitch, and prominent CTAs (Log in/Sign up + Browse
agents) — consistent with the gatehouse and hero
- regression QA: smoke-tested all 9 public surfaces (tavern, browse, mansion,
booth gate, gatehouse, status, changelog, docs, PM gate) — every one renders
with zero console errors; API, Git, Webagent all 200
- verified live: rich booth gate renders with both CTAs
- the home page is long now (hero -> flow -> features -> session preview ->
activity -> footer); add a floating back-to-top button that fades in once
you scroll past the hero and smooth-scrolls the home container to top
- listens on the gm-home scroll container (not window); hidden by the
persistent-home display:none when off-route; respects reduced-motion
- verified live: hidden at top, shows after scroll, returns to top on click
- Home unmounted on every route change, so returning to the tavern reloaded
the canvas (the 'loading tavern…' flash + image re-fetch each time)
- keep Home mounted in a persistent host, hidden (display:none) when off-route;
non-home routes still render in the keyed transition wrapper
- the canvas IntersectionObserver pauses while Home is display:none, so a
hidden tavern costs nothing — and resumes instantly (not reloaded) on return
- verified live: rAF 60→0 when hidden →60 on return, no loading flash, home
renders + scrolls, inner routes + transitions intact
- previously, any render error would unmount the whole SPA to a blank white
screen (no boundary)
- add a React error boundary around the routed app (all sub-apps: tavern,
docs, pm) with an on-brand fallback — gem, 'Something went off-script',
Reload + System status links — instead of a void
- verified the catch path locally (a temporary ?boom trigger rendered the
fallback), confirmed the boundary is transparent in normal use, then
removed the trigger; verified live renders normally
content (in the docs DB):
- the public Roadmap was factually wrong — git.amerc.ai and the embedded
webagent shown as not-done though both shipped long ago. Rewrote it as an
accurate Shipped / Next / Later roadmap that links to the changelog
- Welcome: agent keys are self-service now (was 'ask an admin')
- removed the redundant internal 'PM roadmap (impact x effort)' doc, most of
whose items have shipped — consolidated to one clean public roadmap
code:
- theme docs task-list checkboxes with the accent color and drop the
redundant bullet (li:has(> input[type=checkbox]))
- verified live: roadmap shows 8 shipped (checked) + next/later, clean render
- Reveal now accepts a tag prop so it can wrap semantic elements
- 'Live in the tavern' activity items stagger-fade-up as they scroll into
view (as <li>, valid list semantics), matching the feature cards
- gatehouse stats now count up from 0, consistent with the home hero
- both respect prefers-reduced-motion
- verified live: 8 items reveal (opacity 1, not stuck), gatehouse Count
renders, 0 exceptions
- minimal dep-free highlighter for fenced code blocks: strings (green),
numbers (amber), keywords (purple)
- deliberately NO comment rule, so '//' inside URLs (https://, wss://) is
never mis-colored or mangled — the common micro-highlighter pitfall
- applied in the DocsApp post-render pass alongside the copy buttons; the
copy button still yields the exact original text
- verified live: 22 tokens colored, both https:// and wss:// URLs preserved
byte-for-byte, code intact
- the 60fps canvas render loop ran continuously even when scrolled out of
view (the home page is long now — wasted CPU/battery while reading below)
- IntersectionObserver pauses the rAF loop at 0% visibility and resumes it
when the tavern scrolls back in; cleaned up on unmount
- verified live: ~60 rAF/s on-screen → 0 off-screen → 60 on resume, 0 errors
- the site had focus styles only on inputs, so keyboard navigation was
invisible across every button, link, nav item, card and CTA
- add a global :focus-visible ring (cyan, gold for primary CTAs) — shows for
keyboard users only, follows each element's radius
- label the decorative tavern canvas (role=img + aria-label); its overlay
hotspots remain separately focusable/labeled
- verified live: Tab focuses a nav button with a 2px cyan ring, matches
:focus-visible, canvas announced as 'Animated tavern scene'
- fix fenced code blocks: reset the doubled inline-code background on
'pre code', readable monospace styling (applies to all technical docs)
- DocsApp decorates rendered code blocks with a hover copy button
- seed a 'Connect your broker' guide (guides/) — the lasting reference for
the supply-side last mile: heartbeat loop (curl + Node) to stay online,
the relay WebSocket, and how it fits together (accurate from the API)
- verified live on docs.amerc.ai: guide renders, 3 code blocks, 3 copy
buttons wired, clean styling
- BoothDashboard had no loaded flag, so returning publishers briefly saw the
'Welcome to your booth' new-user onboarding before their classes loaded
- add loaded-gating + a shimmer skeleton; welcome shows only after load
- same fix for the Mansion 'My showcases' list ('No showcases yet' flash)
- completes the loading-skeleton pattern across all data surfaces
(Browse, Booth, Mansion)
- verified live (throttled): skeleton during load, no welcome flash,
welcome/content only after loaded
- wrap the routed Scene in a key={route} container so each page mounts with
a subtle fade-up — a premium app feel on every navigation
- the wrapper preserves the flex/scroll layout (flex:1, min-height:0, column)
so the per-page scroll containers (gm-home, td-page) still scroll
- respects prefers-reduced-motion
- verified live: transition wrapper present, home + inner pages still scroll
(2813>744), navigation works, layout intact
- the instance-created modal was a terse field list; now it's a 2-step
broker onboarding guide at the point of need:
1. Heartbeat — a copy-pasteable curl loop (real instance id + accesskey)
that keeps the instance online (<30s check-in, verified from the API)
2. Relay — the broker WebSocket + note that the amerc webagent speaks it
- color-coded offline status, code block with copy button
- removes the supply-side last-mile guesswork for publishers
- verified end-to-end via the real publish flow (register → class → instance)
- topbar version (vX.Y.Z) is now a button linking to the changelog — the
natural 'what's new' entry point (moved out of the logo button)
- footer 'All systems operational' pill now links to the status page, with
a hover lift
- both new utility pages are now reachable from natural entry points
- also audited the full home page on mobile (390px): hero CTAs wrap, flow
diagram stacks vertically, session preview stacks, live screen renders,
and zero horizontal overflow — no regressions from recent additions
- verified live: both links route correctly
- the in-game cyan screen no longer shows a fake chart; it now cycles the
real CLASSES / ONLINE / SESSIONS counts (big number + label) over a dim
sparkline, with a cross-fade between metrics
- stats flow from Home into TavernGame via a ref the render loop reads, so
the canvas animation stays running while data updates
- ties the brand centerpiece to real product data — the tavern's TV shows
the live business
- verified live: screen renders the live class count legibly
- new #/changelog route: a vivid vertical timeline of releases with version
badges, titles, dates and highlights (14 entries, 0.30 → 0.51)
- communicates the product's continuous shipping cadence — trust + transparency
- scroll-reveal on each entry; linked from the footer Resources column
(replaced the placeholder Roadmap link)
- verified live: 14 entries render, latest first
- AgentBrowse tracked no loaded state, so on cold load it briefly flashed
'No agents here yet — be the first' before data arrived
- add a loaded flag: show 3 shimmer skeleton cards while fetching; gate the
empty-state and 'publish your own' card on loaded
- skeleton shimmer respects prefers-reduced-motion
- also verified (after a stale-Chrome scare) there is NO auth bug: cookieless
/api/auth/me returns null, fresh logged-out load shows Login (no admin nav),
and the public-agent Use flow works without login
- verified live: skeletons during load, no flash, real card+add-card after
- replace the game-instruction subline with the actual value proposition
(chatbox / web terminal / public URL · no infrastructure, just skills)
- add prominent hero CTAs: gold primary 'Browse Agents', outlined secondary
'Publish yours' — visitors no longer have to discover the scene is clickable
- Georgia-styled buttons with hover lift, sit below the animated tavern
- verified live: both CTAs + value prop render
- 'See a session' block: an animated chat widget that plays a scripted
agent conversation (ship a code change, then expose the preview via
Showcase) with typing indicator + message pop-in, looping
- shows the core product experience (chatting with/driving an agent) that
text alone can't convey; clearly labeled 'a scripted preview of a session'
- side copy + 'Browse real agents' CTA; respects prefers-reduced-motion
(renders the full convo statically) and cleans up its timers
- verified live: widget renders, animates, 0 console errors
- animated 'You -> amerc edge -> Your users' architecture diagram on the
home features section: three nodes (broker+agent / relay+proxy+auth /
chatbox+terminal+url) connected by flowing-dot links
- makes the non-obvious 'agent mercenary layer' model legible at a glance
- horizontal on desktop, stacks vertical on mobile, animation respects
prefers-reduced-motion; wrapped in the scroll-reveal
- verified live: 3 nodes, 2 links render, visible
- new #/status route: probes Tavern/API/Docs/Git/Webagent and shows a
per-service dashboard (pulsing beacon, colored pills, latency, state)
- same-origin /api/health gives real status+latency; cross-origin services
use a no-cors reachability probe with a 6s timeout
- auto re-checks every 20s + manual Re-check; honest footer note on method
- linked from the footer Platform column
- audited PM (whiteboards/netdisk/portfolio) via a throwaway admin — all
clean, no changes needed; account removed after
- verified live: all 5 services operational
- Reveal component: feature & use cards fade-up as they enter the viewport
(IntersectionObserver, staggered), with a :not(.in) pattern so the card
hover-lift is never fought by the reveal transform
- Count component: hero stats ease-out count from 0 on load
- both respect prefers-reduced-motion and fall back to always-visible when
IntersectionObserver is unavailable (no stuck-invisible content)
- verified live: all 3 cards reveal to opacity 1, stats animate
- storefront header 'The mercenary roster' with a value-prop subtitle
- emoji category chips (uses TAG_EMOJI) so tags read as a directory
- always-present dashed 'Publish your own' card in the grid — the roster
never looks empty and it actively drives supply into My Booth
- verified live: header, emoji chips, and add-card all render
- remove dead 3D code: App/GemhallScene/SplashScene/ModelScene (unreferenced
since the 2D pivot) + the three dep and its empty manualChunk
- drop public/models (68MB of unused KayKit/poly-pizza GLBs)
- dist drops ~36MB -> 2.9MB; deploy now extracts to a staging dir and swaps
atomically (preserving .well-known), so users never see a mixed/stale state
and stale chunks no longer accumulate (28 -> 7 assets on the server)
- booth: welcoming first-run onboarding (3 steps to go live) replacing the
bare 'no classes yet' hint
- verified live: 2D home renders, 0 failed requests, booth welcome shows
- replace the lonely floating login form with a two-column 'Join the tavern' page
- left: pitch — headline, three benefit rows (hire/publish/deliver) with icon
tiles, cross-app SSO note, live stats
- right: the auth board (login/signup) in a clean static card
- responsive: stacks form-first on mobile (<=860px)
- verified live on desktop + mobile
- replace barren login gate with a full services hall visible to everyone
- hero banner (emblem, value prop, cyan accent, login CTA)
- Showcase room: LIVE badge, feature chips, subdomain URL preview, and
'log in to claim a subdomain' CTA when logged out; full register UI in
- Netdisk relay + Hosted webagent as designed SOON rooms (shimmer, feature chips)
- new mansion hall CSS; drop old thin mn-card/mn-soon styles
- verified live logged-out render at desktop width
- amerc-api: GET /docs + /docs/:id are public (reads); writes still require auth
- DocsApp/ConsoleShell: publicView mode renders docs read-only when logged out,
with a 'Log in to edit' header CTA; edit/new/delete hidden for anonymous
- Docs nav link now shown to everyone
- seeded 5 quality guides: Quickstart, Publish an agent, Embed a chatbox, Showcase, API reference