paint() set fillStyle to black, then to the requested colour — but an
unparseable colour is a silent no-op on fillStyle, leaving black, which measures
as a spurious ~21:1 pass. Assign the colour against two different sentinels and
throw when it doesn't land on the same value, so a broken colour surfaces the
gate instead of clearing it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The stand tier's fork/secret protection is the protected Environment's reviewer
gate, not the job's label condition — a fork PR can carry the label but blocks
on a human before any secret. Say so, note the tier now lives in its own
workflow, and document the E2E_STAND_* secrets and their E2E_* tool names.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Route both the knowledge and template editor inputs through a shared
typeIntoEditor helper (pressSequentially), instead of fill() on one and a
documented ProseMirror-race workaround on the other.
- Make the mkdir spec type a name distinct from the dialog default and pin it, so
a broken input→payload binding no longer matches the default.
- Derive the stand login-readiness locator from E2E_USER instead of a hardcoded
admin@ (and a "flows" button that does not exist).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Release a subscription id's prior handler before overwriting it, so a reused
live id can't leak both subscribers onto one id.
- Assert a stream without `complete` delivers frames but stays open, so flipping
the completion default would fail the protocol suite instead of passing.
- Correct the connection_init comment: a pre-ack frame closes the socket as a
protocol violation, it does not cause a reconnect storm.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Reject a non-object JSON body in the mock-LLM (JSON.parse('null') parsed but
reading .tools then threw and killed the process).
- Pin serve-dist's PORT from the config so an ambient PORT can't move it off the
port Playwright waits on.
- Fail lint on any warning, so a Playwright test with no assertion (expect-expect
is a warning) can no longer lint clean.
- Add a CI check that regenerates src/graphql/types.ts and diffs it, so the
compiled operations can't drift from the codegen input the stand validates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Drop the screenshot threshold to 0.02 (baselines and CI both render in the
pinned container, so there is no host rasterisation noise to absorb) so a
palette change now fails the visual gate; verified 20/20 still match.
- Gate the visual guard on a container marker set by run-visual.sh and the CI
job, not the host OS, so an --update on a Linux workstation cannot overwrite
baselines with host-font pixels.
- retries:0 on the hermetic mock tier so a retry-recovered race fails instead of
merging green; wire @quarantine via grepInvert as the escape hatch.
- Scope globalTimeout to the mock tier so a real-tier run is not aborted mid-retry.
- Verify the visual container against @playwright/test's actual version instead
of a hardcoded literal.
- Key the report's "snapshots differ" advisory on the diffs artifact (uploaded
only on a real snapshot-step failure), not the visual job conclusion; and stop
a transient jobs-API error from silently leaving a stale sticky comment.
- Drop the unconsumed blob report that doubled every uploaded trace.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Track sequenced-response consumption by entry identity, not a cursor into the
current eligible subset: a second raised flag grew the subset, reset the cursor
to 0, and replayed the previous flag's entry once before advancing.
- Restrict isPlainObject to true plain objects, so a non-plain pin (a Date) can no
longer recurse into empty own-keys and match any object-shaped value.
- Key subscription streams on the request's variables and REST sequences on the
request body, so entries without variables no longer merge onto one cursor.
- Complete a late subscriber that joins after a complete:true stream has drained.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The seeded /info returned privileges:[] with no OAuth providers — a state the
backend never returns. Every privilege-gated control (e.g. the knowledge
Anonymize action) and the login page's OAuth buttons rendered in their denied
variant in every spec and tier, so a regression there shipped unseen.
Seed the admin privilege list and the OAuth providers (authenticated and guest),
assert the login page now renders its OAuth buttons, and refresh the /flows/5
baseline, which now shows the flow controls in their real admin state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The knowledge list query is withContent:false, where the backend returns an
empty content string; the entry served "Content for …".
- The generator and refiner prompt pairs were inverted vs the backend
(System/Human swapped), teaching the wrong contract on two of fifteen agents.
- The GraphQL mock dropped an entry's data when it also carried errors, so a
partial-error response could not be mocked with its data half.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The entry answered HTTP 200 with `{error, status}`, but the backend returns 401
with `{status, code, msg}` (develop mode adds the `error` key). The two take
different app branches: the 200 shape resolves and shows "Invalid login or
password"; the real 401 is caught by the axios interceptor, which clears the
auth key and rejects, so the form shows "Login failed. Please try again." — the
branch a real wrong password takes, which no spec exercised.
Serve 401 with the real envelope, pin the submitted credentials with bodySubset,
and assert the message the 401 path actually produces.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The gate subscribed to `labeled` so applying `e2e:stand` could start the stand
job, but every gate job carried `if: github.event.action != 'labeled'`. A label
event then ran with e2e-mock skipped, and GitHub counts a skipped required check
as satisfied on the same head SHA — so adding any label to a PR whose e2e-mock
was red published a merge-permitting check over the red one.
Move the stand tier into its own workflow (e2e-stand.yml) that owns the
`labeled` trigger; e2e.yml now fires only on real code events (opened /
synchronize / reopened) and its gate jobs never skip. The stand workflow keeps
the protected environment + reviewer gate, and uploads results.json only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep only the two invisible contracts — pre defers its colours to .hljs, and
data-language is DOM-only and never serialized — and drop the prose that
restated the CSS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guards the two-way mapping added with the extra_body field: a JSON object
survives GraphQL→pconfig→GraphQL intact, and an absent one stays nil rather
than becoming an empty map.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The backend already threaded a per-agent `extra_body` map into the LLM request
(pconfig.AgentConfig, openai.WithExtraBody), but it was reachable only from YAML
provisioning — the GraphQL type, the converter, and the settings form all
dropped it. Editing a YAML-provisioned provider through the UI therefore round-
tripped its config and silently stripped extra_body.
Add `extraBody: Map` to AgentConfig / AgentConfigInput, carry it through the
converter in both directions, and give each agent an "Extra Body" JSON field in
the provider form (validated as an object, serialized on load, parsed on save).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The prev/next stepping control sat first in the header's right-anchored action
cluster, so a per-entity action that appears after load or differs between
entities (e.g. the flow Report button) pushed the pager sideways — clicking Next
repeatedly landed on whatever slid under the cursor.
AppHeaderActions now takes a `pager` slot rendered as the cluster's trailing
child: its right edge is pinned to the header edge and its width is constant, so
variable actions grow leftward without moving it. flow / template / knowledge
detail headers route their DetailNavigationToolbar through the slot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fenced code in the editor rendered as one flat grey block with no colour and no
visible language, even though the fence language was already captured into the
node and round-tripped. Swap the plain StarterKit codeBlock for CodeBlockLowlight
(lowlight + highlight.js are already deps; the tiptap wrapper is the one add),
keeping the byte-fidelity parseMarkdown/renderMarkdown tuning verbatim — the
highlighting is a view-only ProseMirror decoration and never touches the
serialized markdown. renderHTML stamps the language onto the `<pre>` as
data-language so a CSS caption bar names the block, matching the read-only
viewer's atom-one-dark theme.
Verified: all 337 existing editor round-trip tests still pass, plus a new one
asserting the fence language reaches attrs.language and survives save for
ts/python/bash/json/go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mock world's matchers carried five explanatory comments and no test. Cover
them directly — order-independent variable matching, nested-object subset,
sequenced-then-repeated entries, a mismatched REST body staying unmatched, and
flag-gated visibility — and delete the prose they replace. The order test is
built key-by-key on purpose: a literal would be re-sorted by the linter, which
silently defeated the first version (a stripped key-sort still passed until the
value was moved inside an array, where stableStringify actually normalises it).
Sweep the rest of e2e for comments that only narrate the test or restate the
code — step labels, "happy path", cassette descriptions, field docstrings — and
remove them. Kept: framework gotchas whose violation is silent (a probe reading
a colour mid-transition, react-hook-form's disabled Submit, a pre-ack frame that
storms graphql-ws), security notes, magic-value and source-of-truth pointers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each sandbox gets a `<container>-data` volume created straight on the docker
socket, outside the compose project, so removing the container left the volume
behind and every Tier-2 run added one more. Confirmed on this machine:
pentagi-terminal-90001-data and -90002-data outlived their containers.
Swept with the same anchored 9xxxx filter the container removal uses, so a dev
stack's volumes are left alone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three ways these gates could go green while measuring nothing:
The editor probes name their classes by hand — the one place in the contrast
spec that does not import them from the app. A rename, or the rule moving out
from under its ancestor chain, would mount a span matching no rule, and the
measurement would fall back to the page's default text pair, which clears AA.
The probes now reject a transparent background, which only an unmatched class
produces (the check lives with the editor probes, not in the shared measure —
the outline badge legitimately paints no background).
The dark sweeps seed a theme key that mirrors a *default parameter value* in the
provider, so it can stop matching without any test file changing; the run would
then repeat the light theme under a dark label. Both sweeps now assert the theme
actually applied.
The resources target-size waiver matched `.rounded` unanchored, so it also
covered every `rounded-*` utility — a too-broad waiver never fails, it only
hides. Anchored to the class it documents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`toHaveClass(/destructive/)` on the delete confirm matched every shadcn Button —
the base class string carries `border-destructive` and `ring-destructive/*` for
the invalid state — so the one guard against an action losing its destructive
variant, a regression this repo already shipped once, was dead. Anchored on the
variant's own `bg-destructive` fill instead.
The message-alignment check took `.items-end` first-match anywhere on the page
and never asserted the contrasting case; it now binds to the input message and
pins the count, so an inverted ternary fails on both.
`not.toHaveClass(/dark/)` also holds before the provider has applied any class,
so the light half of the theme switch proved nothing; the provider sets an
explicit `light`, so assert that. Same shape as the menu-item absence check,
which now anchors on an item that stays.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keying the waivers by route path made the spec restate the manifest's sample
flow id, so renaming that id would silently unhook the waiver and redden a route
over debt that was already accepted. The waivers now live on the manifest entry
and travel with it; scanA11y takes the list directly, which also drops the
route-keyed map the hand-written sweep needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sweep hand-listed six scans while the manifest holds nine routes, so
/dashboard, /resources, /settings/prompts and /settings/providers were never
scanned in either theme — and since the pixel matrix cannot see a colour change
either, those pages had no automated accessibility instrument at all. It now
iterates the manifest, so a route added to the sweep is scanned by construction.
The four newly covered routes surfaced real debt on the first run. One was a
genuine invalid-ARIA bug and is fixed in its own commit; the rest are recorded as
named waivers: the dashboard period switcher points aria-controls at a tab panel
it never renders, and the resources tree has 80%-muted row metadata under AA plus
an expand toggle and row checkboxes under the 24px pointer-target floor. Each
waiver names one defect and matches only its own nodes, so the same rule still
fires anywhere else on the page.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Seven payload-bearing cassette entries answered success no matter what the app
sent: the four flow mutations, both create mutations, and the mkdir POST. Since
each assertion then reads canned cassette data, a wrong payload — a stale closure
deleting flow 6 while flow 5 is open, an unbound name field — repainted exactly
the asserted UI and the suite stayed green. Verified by flipping a pinned flow id
and a pinned path: both specs now fail on the unmatched request, and both passed
before.
Matching had to grow a level for this: `bodySubset` documented a deep subset but
compared nested values whole, so `{ input: { name } }` demanded the entire input.
It now recurses, which is also what makes the API-token pin possible — its `ttl`
is derived from the clock at submit time and drifts a second between runs, so
only the operator-entered name is pinned.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The listing is a `tree`, not a grid, so there is no columnheader for `aria-sort`
to live on — axe rates it a critical `aria-allowed-attr` violation on every
sortable column. The sort state is already announced through the button's label
("Sort by name (ascending)"), so removing the attribute loses nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The probes carry `transition-colors`, so reading the computed background right
after hover() sampled a colour still interpolating away from the rest state —
the half of the gate that exists to catch hover regressions could not catch one.
Disabling the transition on the probes fixes it: verified by giving one variant a
deliberately unreadable hover fill, which the assertion now reports as "badge
blue on hover" and previously let through.
Key the probe list off the variant union so a new badge variant fails to compile
until it is probed, rather than silently going unmeasured.
Also correct two route-manifest ownerships that misdirect diff-scoping: the flows
provider is mounted by the detail route as well as the list, and the provider
detail page was attributed to the list route that never renders it.
Doc fixes: the route sweep is tagged @cross, not @coverage; the licenses link in
CONTRIBUTING resolved outside the repository; and the frontend prettierignore
duplicated entries prettier already reads from gitignore.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four independent ways the signal misled:
Adding a label to a PR skips every gate job, yet the report still ran, found no
results file and overwrote a correct green comment with a red "no results". It
now returns early when the run carries no completed mock job.
A run aborted by the global timeout still uploads a results file whose failure
count is zero, so the header could go green over a red job; the verdict now
requires the mock job's own conclusion too.
The report's concurrency key was the branch name alone, so a fork PR opened from
a branch named `main` lost its comment to upstream `main` activity. The gate's
key split `opened` from `synchronize`, leaving both runs live so the older one
could finish last.
Stand failure screenshots carried the stand URL and the login into a public
artifact — screenshots are now tier-aware like traces and videos already were.
Also guard the Tier-2 teardown: the trap is the script's last statement, so a
teardown hiccup turned a passing run red.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Success" badge and the all-passed accordion badge were assembled from raw
utility classes (`text-green-600` on a green tint, no dark override) instead of
the shared variant, so they kept the pre-unification shade and render at about
3:1 in light theme — under the 4.5:1 floor every other colored badge now meets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sticky PR comment took its header from the whole-workflow conclusion, so a
visual-snapshot diff — advisory, never a required check — rendered a red ❌ over a
"62 passed, 0 failed" mock-tier stats line, reading as a gate failure it isn't.
Track the header on the mock gate instead (✅ pass / ❌ real failure) and surface a
differing visual job as a separate ⚠️ line pointing at the diff artifact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A used template variable read as green in the Available-variables panel (its
"used" badge) but violet in the editor highlight — the same entity, two colours.
Swap the editor variable to green and the <xml-tag> highlight to violet so it
stays distinct. A pure hue swap: lightness and chroma are untouched, and every
token still clears WCAG AA in both themes (tightest is the dark tag at 5.36).
Guard the editor palette in cross/contrast.spec.ts alongside the badges, mounting
the real .template-* rule so the shipped CSS is measured, not a copy — verified to
fail when a token is pushed under AA.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The last two uncovered detail tabs. Files turned out not to be REST-backed at all:
it runs its own flowFiles query, separate from FlowDocument, so it needed a keyed
cassette entry and paths under the manager's uploads/resources prefixes — a file
seeded outside those roots renders nowhere. Screenshots reads FlowDocument like the
other four; only its image is fetched over REST.
That image is the part worth guarding. It mounts behind an intersection observer
and loads lazily, so a spec that merely opens the tab never requests it — the first
version passed with the cassette entry removed. It now scrolls the card into view
and asserts the decoded width, since toBeVisible is satisfied by an element that
failed to decode. Removing the entry fails it twice over: zero width, and the
unmatched-call gate.
Serving those bytes needed the REST mock to stop assuming JSON, so entries can now
declare a content type and have their body passed through verbatim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nothing enforced the container requirement — only a comment mentioned it. Running
the visual project on a developer machine writes `*-visual-darwin.png` next to the
linux baselines: new files CI never reads, so it stays red while the repo collects
junk and nothing says why. Fail up front with the command to use instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Strict pixel comparison was stable across seven local container runs and failed on
the first CI run: /flows differed by 332 and 384 pixels, identically on both
attempts, so it is a systematic host difference in text rasterisation rather than
jitter. The pinned image fixes the renderer, not the machine under it.
A budget wide enough to absorb 400 pixels cannot notice the ~70 a palette change
moves, so this trade has no useful setting — pixels are the wrong instrument for
colour. Back to the default tolerance, with the reasoning recorded so the next
attempt starts from the CI measurement rather than a local one.
The masking-race fix from the same commit stays: it was a real defect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The suite was carrying Playwright's 0.2 per-pixel default, a tolerance meant for
cross-machine rendering noise that the pinned container removes by construction.
It cost the gate its sensitivity: a real palette change passed while three
baselines silently went stale.
Compare strictly and bound the differing-pixel count instead. Measured over
repeated container runs, glyph antialiasing jitters by exactly 2 pixels, while the
palette change that slipped through moved 71 — so threshold 0 with a 20-pixel
allowance tolerates the noise and catches the class of change that got away.
Strict comparison also exposed a real defect the loose one hid: the flow detail's
ready locator was the header button, but the visual spec masks the terminal, which
mounts later. Capturing in that window compared live terminal pixels against a
masked baseline — a 261k-pixel diff, roughly a third of the frame, appearing at
random. The route now counts as rendered when its terminal exists.
Seven consecutive container runs are clean at the new settings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Green sat a step darker than its siblings because green-700 measures 4.4991
against its tint — imperceptibly short of AA, but short. Carrying that as a
one-variant exception meant an odd shade number in the map, and the only ways to
let green back to 700 were to lower the gate for all eleven variants or to waive
it in the spec, which is the same exception wearing a different hat.
Move all seven to 800 instead. One rule, no exception, and the whole set clears
the bar with room: 6.39–7.83 on light, against 4.61–6.49 before — orange and
yellow had been sitting close enough that a later tint tweak would have pushed
them under. Dark is unchanged.
Three visual baselines were quietly stale: the earlier 600→700 move changed blue
text that the pixel comparison tolerated, since its YIQ metric under-weights a
blue-channel shift with little luminance change. Regenerated; two clean runs
confirm they are deterministic.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The palette fixes had no guard: no scanned page renders a colored badge or a
destructive fill, so a shade edit could silently drop a variant under AA and
nothing would fail. Two comments were standing in for that coverage.
Mount one probe per variant from the component's own cva — a class list copied
into the spec would only ever re-test itself — and measure what the browser
actually paints, so oklch tokens and translucent fills are read as rendered.
Every variant is checked at rest and on hover, in both themes.
The measurement is deliberately unrounded: green-700 comes out at 4.4991, which
two decimals report as a passing "4.5". With rounding in place the guard passed
against a deliberately regressed green; without it, it fails and names the
variant, the state and the ratio.
Specs can now import component modules, so the e2e tsconfig gains jsx.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three comments added with the contrast fixes were mostly narration: two restated
the class strings they sat above, and two defended the implementation rather than
naming a contract. The one fact that is not visible from the code — green sits a
step darker than its siblings — belongs in a test that fails when it regresses,
not in prose asking the reader not to touch it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four states were under AA, not one: the dark badge and button at rest (3.71), the
light badge on hover (4.34) and the dark button on hover (4.40), plus the dark
destructive menu item (4.01). Badge and button had also drifted apart — one faded
to 80% on hover, the other to 90% — so each failed in a different theme.
The token itself cannot be retuned: sweeping its lightness finds no value that
serves both roles, because the same red is a fill under white text and the text
colour on the page. Darkening it for the fill would break the twenty-four
text-destructive call sites.
Tune the fill's opacity per theme instead, keeping white text and adding no tokens.
A translucent red composites toward what is behind it, so the same fade darkens it
on the dark ground and lightens it on the light one: full strength on light fading
to 90% on hover, 80% on dark dropping to 70%. Hover now raises contrast in both
themes. All four states land between 5.04 and 6.28, verified on a built bundle
against every dark surface token (worst case 5.13 on secondary).
The dark destructive menu item drops its focus wash from 20% to 10%, matching light
and restoring the label to 4.52 at the cost of a fainter highlight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every colored Badge variant carried a single text shade for both themes, and no
one shade clears WCAG AA on both grounds. Measured against the current tints,
five of the seven failed on light and four on dark, with red and pink failing on
both — the badge text is 12px semibold, so the bar is 4.5:1.
Use the darker shade on the light tint and the lighter one on dark:
text-{color}-700 with a dark:text-{color}-400 override. Green takes 800 instead
of 700 because a shade number is not a constant lightness across hues — green-700
measures 4.4991, just under the bar.
Every variant now lands between 4.6 and 9.7. The knowledges a11y waiver is gone
and both theme scans pass without it; the knowledges dark visual baseline is
regenerated for the lighter blue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measuring the variants (browser-resolved sRGB, the 10% tint composited over the
real card surface, WCAG formula) showed the defect is not dark-only: five of the
seven colored variants fail AA on light and four on dark, and no single text shade
clears both grounds. Blue happens to pass on light and fail on dark, which is why
only the dark scan flags it here. The waiver itself is unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only the Terminal tab had a spec; Tasks, Agents, Searches and Vector Store — all
fed by the FlowDocument query — were uncovered. Add a populated flow cassette (one
entry per tab) and a spec that opens each tab and asserts its seeded content
renders. Files and Screenshots are REST-backed and left for a follow-up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The route-manifest visual sweep only captured the empty providers state. Add a
populated cassette (enabled defaults + a user-defined provider row) and a dedicated
visual spec with light/dark baselines, generated in the pinned container. Dates
render deterministically because the visual project inherits the mock clock via the
backend fixture.
Also bind --update-snapshots=all in run-visual.sh so a trailing spec-file filter is
not swallowed as the option's optional [mode] argument.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The suite tags every spec but the docs only hinted at "@smoke, …". List the real
tags (@smoke/@flows/@crud/@coverage/@cross/@visual/@real/@stand) with their meaning
and how to filter, and spell out the two conventions the mock gate reserves:
@quarantine to isolate a flaky spec (excluded via --grep-invert) and @generated for
recorder-derived cassettes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
axe only ran in the light theme, so dark-only contrast regressions were invisible.
Parametrize every a11y scan over light and dark. The dark pass surfaced a real,
pre-existing issue: colored Badge variants use text-{color}-600 with no dark
override and fail contrast on the dark background. Waive it narrowly on the
knowledges list (the blue badge) as named debt — a palette fix is queued
separately — so the rest of the dark surface is now gated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The period-switch test only proved the usageStatsByPeriod refetch fired; it would
still pass if the UI rendered stale data. Key the cassette's usageStatsByPeriod by
period (week vs month) with distinct date ranges, and assert the Token Usage chart
x-axis shows the month range after the switch and drops the week range.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The comment claimed no non-backslash byte is lost, but GFM legitimately trims
cell-edge and collapsed whitespace, so the test asserts the weaker true invariant:
every byte other than backslashes and that trimmed whitespace survives in order.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The base compose file pins fixed network names, so any network left un-overridden
in this e2e override joins the stack to the developer's live observability and
langfuse networks. Give all three networks e2e-scoped names. Add a healthcheck to
the mock-LLM service and make pentagi depend on service_healthy, so a crash-looping
mock can never read as a started dependency and silently mask itself.
Update frontend/docs/e2e.md to match reality: the stand tier runs @stand only
(flow-run drives a real paid agent and stays local), every mock run rebuilds its
own bundle by design, and the trend/affected-routes notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The co-located components/shared/README.md documented hooks that no longer
exist. `usePagination` was never a hook, and the writer `useTableQueryFilter`
was replaced by the unified `useTableState`, so the copy-paste recipe it shipped
would not compile. Detail pages had likewise moved from spreading a `toolbarProps`
object to passing a `controller` prop.
Rewrite it as frontend/docs/list_detail_pages.md, matching the flat
per-subsystem layout backend/docs already uses: exact signatures for every
hook, component and storage helper, the wiring the five live list pages
actually use, and an explicit table of removed names so they are not
reintroduced. Drop the source-adjacent README — no other directory under
frontend/src or backend/pkg carries one.
Fix the comments that still named the removed hook, plus two claims in them
that contradict the code: the controlled filter is URL-backed only (DataTable
persists sorting, column visibility, page size and search columns, never the
filter), and `busy` on InlineEditInput disables the Save and Cancel buttons
while leaving the input editable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dashboard partial-errors spec matched cards via getByTestId('dashboard-card'),
but that attribute lives only in an uncommitted working-tree change, so on a clean
checkout the locator matched nothing and the spec failed. Match the data-slot="card"
attribute the shadcn Card primitive already emits. Also lift the inline xterm buffer
type in the message-rendering spec into named types so it satisfies prettier and the
union/module sort lint rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>