Commit Graph

324 Commits

Author SHA1 Message Date
Alem Tuzlak 42d3c92fbd chore: merge origin/main into tyler/default-browser-inspector 2026-08-27 12:09:27 +02:00
Tyler Slaton d44178a8f0 fix(web-inspector): match Playground composer surface 2026-08-26 19:31:36 +02:00
Tyler Slaton 76f3e9fff4 fix(web-inspector): repair Inspector verification baseline 2026-08-26 19:23:38 +02:00
Tyler Slaton edbbdbdf80 fix(web-inspector): restore Playground surface styling 2026-08-26 19:22:45 +02:00
Alem Tuzlak 5a188d6783 fix(web-inspector): animate launcher hover scale and color 2026-08-26 09:25:41 -07:00
Alem Tuzlak 93861b428d fix(web-inspector): polish inspector chrome, threads, and dark mode 2026-08-26 09:25:41 -07:00
Tyler Slaton e8a5e00e8b feat(web-inspector): preview launcher HUD on load 2026-08-26 17:41:32 +02:00
Alem Tuzlak fd7f2fa683 Merge origin/main into alem/hud-arrow-color 2026-08-26 11:36:50 +02:00
Tyler Slaton a6751df682 fix(web-inspector): theme launcher HUD with inspector 2026-08-26 03:40:30 +02:00
Tyler Slaton d2e0c35cae fix(web-inspector): align launcher HUD shell styling 2026-08-26 03:38:32 +02:00
Tyler Slaton d2a6eee07a fix(web-inspector): align launcher HUD availability 2026-08-26 03:29:38 +02:00
tylerslaton 9629e930d1 chore: release monorepo v1.69.2 2026-08-26 00:18:42 +00:00
Tyler Slaton b3b339f544 Revert "feat(web-inspector): add Event Snippets and save-as-snippet (#6649)"
This reverts commit ba4260ad66, reversing
changes made to 47c5510b49.
2026-08-26 02:11:19 +02:00
Mike Ryan b3c3cb0d7b test(web-inspector): stop the gesture tests racing the beat on real timers (#6693)
## Problem

`packages/web-inspector/src/__tests__/launcher-error-signal.spec.ts` is
failing intermittently on `test / unit`, across unrelated branches and
on `main`. It is currently red on the **v1.69.1 release PR**.

```
FAIL  src/__tests__/launcher-error-signal.spec.ts >
      the whole gesture completes on its own and leaves the resting state behind
AssertionError: expected 'opening' to be 'closed'
```

| When | Branch | Shard | Run |
|---|---|---|---|
| 19:29Z | `release/publish/monorepo/v1.69.1` | Node 24 / React 18 |
[32886136479](https://github.com/CopilotKit/CopilotKit/actions/runs/32886136479)
|
| 14:09Z | `ben1/oss-924-agui-core-058` (#6687) | Node 24 / React 19 |
[32857130830](https://github.com/CopilotKit/CopilotKit/actions/runs/32857130830)
|
| 12:41Z | `lukas/oss-903-presentation-…` | Node 20 / React 18 |
[32848616256](https://github.com/CopilotKit/CopilotKit/actions/runs/32848616256)
|

Neither #6687 (an `@ag-ui/core` version bump) nor the presentation
branch touches gesture timing, and no shard fails consistently — it
follows runner load, not code.

## Cause

Both real-timer tests in this suite asserted a *pre-beat* state 200ms
after breaking the connection:

```ts
const context = await setup({ realTimers: true });
await context.breakConnection();
await context.advance(200);
expect(pillPhase(context.inspector)).toBe("closed");   // ← races the beat
```

Under `realTimers`, `advance(ms)` is a literal `setTimeout(resolve, ms)`
(spec L656–664). The pill's `closed → opening` transition fires at
`ERROR_GESTURE_MS.beat = 400` (`index.ts` L330–339, scheduled at
L18998). So the assertion had a **200ms margin against a 400ms boundary
on a wall clock**. On a loaded runner the 200ms sleep overshoots 400ms,
the beat has already fired, and the phase reads `opening`.

The comment directly above the test already says phase boundaries are
asserted on the fake clock "because real timers would make this suite
slow and flaky" — and then this was a phase-boundary assertion on real
timers.

The same 200-vs-400 race sat in the adjacent test (`pulsing` is true
only for the beat's 400ms), so both are fixed here.

## Fix

Remove the two racy preconditions. Both claims are already pinned
deterministically on the fake clock at spec L411–427, which asserts
`pillPhase === "closed"` **and** `pulsing === true` right after arming,
then walks every phase boundary. These real-timer tests exist only to
show the beat and the gesture run to their end on their own — which the
loops and their closing assertions still prove.

This also matches the idiom the sibling `launcher-signal.spec.ts`
already uses (L667–676): assert at t≈0, then poll for the end.

## Testing

**1. Reproduced the CI failure locally.** Injected a 250ms stall before
the assertion on the unmodified test, simulating a loaded runner (200ms
sleep + 250ms ≈ 450ms > the 400ms beat):

```
FAIL  src/__tests__/launcher-error-signal.spec.ts > the whole gesture completes on its own …
AssertionError: expected 'opening' to be 'closed' // Object.is equality
Expected: "closed"
Received: "opening"
 ❯ src/__tests__/launcher-error-signal.spec.ts:2666:40
```

Byte-for-byte the CI assertion.

**2. The fix survives that same simulation.** With a 700ms stall (well
past the beat) injected into both tests:

```
 ✓ the beat ends and leaves the resting dot behind  750ms
 ✓ the whole gesture completes on its own and leaves the resting state behind  3435ms
 Tests  2 passed | 88 skipped (90)
```

**3. Mutation-checked that the remaining assertions still have teeth.**
Three separate breaks to `src/index.ts`, each caught:

| Mutation | Result |
|---|---|
| Gesture opens but never closes (drop the `closing` phase +
`endGesture`) | `FAIL … AssertionError: expected <span …> to be null` |
| Pill never opens at all (`openPill` returns early) | `FAIL …
AssertionError: expected false to be true` (`sawOpenPill`) |
| Beat never ends (`beat: 400` → `999_999`) | `FAIL … AssertionError:
expected true to be false` (`pulsing`) |

Source restored afterwards; `git status` confirms this PR touches only
the spec file.

**4. Full `@copilotkit/web-inspector` suite:**

```
 Test Files  29 passed (29)
      Tests  625 passed (625)
```

**5. Pre-commit gate** (`test-and-check-packages`: test, publint, attw
across 5 projects + 22 dependencies, incl. `@copilotkit/react-core`,
`@copilotkit/angular`, `@copilotkit/runtime`) passed on the committed
tree.

## Note

`main` also has a second, unrelated flake I did not touch here —
`CopilotChatToolRerenders.e2e.test.tsx > should not re-render a
completed tool call when subsequent text is streamed` (`expected 4 to be
3`), which reddened `main` at `0943c519` ([run
32750372113](https://github.com/CopilotKit/CopilotKit/actions/runs/32750372113)),
a runtime `.d.ts` change that touches no react-core chat code. Different
mechanism, worth its own issue.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-08-25 14:22:03 -07:00
Tyler Slaton 79c02f0f02 style(web-inspector): fewer layers on the launcher, a lens for its dot (#6688)
## What does this PR do?

Design review on the launcher and its notification dot asked for three
things: a milder face than solid black, fewer borders and background
layers, and a subtle shadow in place of the dot's heavy border. This is
all three, plus the removal of six utilities that never had any effect.

Everything here was compared side by side at production size, on a light
*and* a dark host page, before it was chosen. Two of my own first
proposals were dropped after measuring them, both described below.

**Two concerns, three commits.** `4fa38d91f` and `cd711b369` are the
launcher itself — the package change, 2 files. `295f75495` gives the
react-router lab a dark mode, because a dark host page is what this
change has to be judged against and the lab could not produce one. If
you would rather review those separately, say so and I will split them.

## The face

`#181C1F` at 95%, which review asked for.

Worth recording so it does not come up again: the near-black the review
saw was `#010507`, 20.5:1 against white. What shipped yesterday was
already `#1C1F24` at 16.5:1, so this value is a hair *darker* than the
one it replaces (17.2:1) and the difference between them is a ΔE of 2.3,
at the floor of what an eye can separate. It settles the question rather
than changing the look.

## Fewer layers

Six Tailwind utilities on the launcher set properties the unlayered
`css` block sets again — `bg-slate-950/95`, `border-white/20`, `ring-1`,
`ring-white/10` and the two hover variants. Unlayered declarations beat
layered ones regardless of specificity or source order, so none of them
has ever had any effect. Each was the package's only use, so the
checked-in stylesheet drops 980 bytes.

Of the *visible* layers, two went:

**The outer hairline.** The launcher carried two concentric lilac rings:
the border, and a second one 1px outside it as a box-shadow. The outer
one also hardcoded the lilac rather than reading `--cpk-launcher-edge`,
so it silently could not follow the token. It is replaced by a one-pixel
light edge along the top, which is what keeps the face from reading flat
without drawing a frame.

**`backdrop-blur-md`.** It sat behind a 95%-opaque fill and bought close
to nothing, while mounting a permanent blur compositing layer over a
customer's page.

**The border stays, and this is the finding that changed my mind.** I
first proposed removing it too. Against a dark host page the face
measures 1.10:1 (GitHub dark), 1.04:1 (Tailwind slate-900) and 1.22:1
(black) — indistinguishable from the page. The border is the only thing
that gives the launcher an outline there. It is not decoration.

## The dot

The collar was `1.5px`, opaque, zero blur, and 21% of the dot's
footprint. Because the dot's centre sits *on* the rim, its outer half
painted a hard dark crescent onto the **host page** rather than onto the
launcher — which is what read as "heavy". A hairline plus a soft drop
separates it just as well.

The fill becomes a lens lit from the upper left. Both stops are derived
from `--cpk-launcher-signal`, so a new tone needs no new values;
verified for the rose error tone and the violet announcement tone.

**Dropped after looking at it:** a coloured glow around the dot. It was
the obvious reading of "more premium", but the launcher already pulses
in that same colour when a failure is new, and a permanent glow competes
with the thing that is supposed to draw the eye.

**Also dropped:** tinting the border in the signal colour, which was
suggested in review. On a dark page the border is the entire silhouette,
so tinting it recolours the whole launcher for a state that can persist
for hours.

## One non-obvious consequence

Removing the blur removed a side effect nobody had written down:
`backdrop-filter` promotes the element to its own compositing layer.
Without a layer, the hover `scale(1.05)` re-rasterises the mark every
frame and it visibly jitters — geometrically nothing moves, the mark's
centre holds to three decimals, but the vector is re-rendered at
fractional offsets. `will-change: transform` asks for the layer directly
and the jitter is gone. Confirmed by eye on the running demo before this
was chosen.

## Tests

`packages/web-inspector` stays at **28 files / 611 tests**, all passing.

No new tests. The colour tests here are deliberately token-shaped rather
than value-shaped — they assert the custom property and the *sharing* of
one face and one edge between the launcher and its pill, never a hex —
so face and edge values are free to move and this change is exactly the
kind they were written to allow. The one test that constrains it, `"the
pill and the launcher share one surface and one edge"`, still passes.

What is genuinely unguarded, and was before this PR too: the dot's
collar width, the double hairline, and the Tailwind class list.
Asserting rendered geometry would need a browser test runner, which this
package does not have — jsdom computes no layout.

## How to see it

`pnpm --filter react-router-example dev`, then `http://localhost:5173`.
The launcher is top right; `Break runtime` arms the error tone and
`Break run` the announcement one. Hover it to check the mark no longer
jitters.

One thing worth knowing while reviewing: the launcher anchors top-right
and is `position: fixed` on an element mounted directly under `<body>`,
so on this page it sits over the lab's toolbar. Drag it to the lower
right and it is out of the way.

## The lab's dark mode

A dark host page is where the launcher's border earns its place, and the
lab had no way to produce one, so reviewing this change was not possible
without it.

It follows `examples/v2/react/demo` rather than inventing anything: the
host owns a `theme` state, and `CopilotChat` gets `className="dark"` —
which is what makes the package swap its own variable set. The colours
are the demo's by another route; it writes the oklch literals
CopilotKit's variables use, and those are Tailwind's neutral steps
(`neutral-950` is `oklch(0.145 0 0)`, `neutral-50` is `oklch(0.985 0
0)`, `neutral-800` is `oklch(0.269 0 0)`). Measured identical on the
running lab.

`@custom-variant dark (&:is(.dark *))` is needed in the lab's stylesheet
because Tailwind v4 points `dark:` at `prefers-color-scheme` by default,
so the toggle would have lost to the OS. Same declaration the package
uses for its own sheet.

Two details that are decisions rather than oversights. The **error
banner keeps a rose tint** in dark mode instead of going neutral,
because an error banner that looks like every other surface is not an
error banner. And the **toolbar buttons keep a visible on/off contrast**
— active inverts to a light face, inactive sits on `neutral-800` —
because the lab's whole purpose is knowing which failure is currently
armed.

My first attempt stripped every background instead of theming, and that
is worth recording because it looked plausible: the chat bubble, the
send button, the button states and the banner all collapsed into one
flat grey. The chat paints its own surfaces and has to be told what
theme it is in, not undressed.

## A separate bug found on the way

`CopilotKitProvider` documents `inspectorDefaultAnchor` — *"Default
anchor corner for the inspector button and window"* — and it has no
effect. `defaultAnchor` is typed on the React wrapper and forwarded to
the element, but the string `defaultAnchor` does not occur anywhere in
`packages/web-inspector`, so it lands as `defaultanchor="[object
Object]"` and is ignored. The corner stays hardcoded `{ horizontal:
"right", vertical: "top" }` in two places.

Not fixed here, to keep this PR to one concern. It is worth fixing: any
host with a top navigation bar hits exactly this, finds exactly that
prop, and it does nothing.

## Related PRs and Issues

- Follows #6646
2026-08-25 13:27:06 -07:00
Benjamin Taylor 04684614ca test(web-inspector): stop the gesture tests racing the beat on real timers
The two real-timer tests in the launcher's error-signal suite each asserted a
pre-beat state 200ms after breaking the connection. The beat is 400ms
(ERROR_GESTURE_MS.beat), so both assertions had a 200ms margin against a wall
clock on a shared runner. When the runner is loaded the 200ms sleep overshoots
400ms, the beat has already fired, and the assertion reports the next phase.

Both claims are already pinned deterministically on the fake clock, where every
phase boundary of the gesture is asserted. The real-timer tests exist only to
show the beat and the gesture run to their end on their own, so the racy
preconditions are removed rather than retimed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 14:46:25 -05:00
MikeRyanDev 6053e4e262 chore: release monorepo v1.69.1 2026-08-25 18:50:37 +00:00
Alem Tuzlak 7509b379e6 fix(web-inspector): remove extra space under the sidebar plan card
The sidebar footer had 18px extra padding under the plan card.

The card now uses the same 12px inset as the rest of the sidebar.
2026-08-25 18:36:29 +02:00
Alem Tuzlak 72f037744f fix(web-inspector): match HUD arrow and drop the SW overlay
The last mix made the HUD arrow a bit too light.

An empty 28px SW resize handle sat on the collapsed sidebar toggle.

In dark mode that handle painted as a black box.

The south and west edges still resize the floating window.

The SE grip is unchanged.
2026-08-25 18:29:55 +02:00
Alem Tuzlak 841aea0614 style(web-inspector): lighten the HUD arrow to match the glass card 2026-08-25 18:12:10 +02:00
Alem Tuzlak f7efb5dc7b style(web-inspector): keep the HUD glass fill and match the arrow to it 2026-08-25 18:08:44 +02:00
Alem Tuzlak 3d5d42c1a0 style(web-inspector): match the HUD arrow fill to the card 2026-08-25 18:04:34 +02:00
Lukas Moschitz cd711b369c style(web-inspector): one hairline on the launcher, a lens for the dot
Review asked for fewer borders and boxes, and for the dot's heavy border to
become a subtle shadow.

The launcher had two concentric lilac hairlines: the border, and a second
ring 1px outside it as a box-shadow, which also hardcoded the lilac instead
of reading the edge token. The outer one goes. What replaces it is a
one-pixel light edge along the top, which keeps the face from reading flat
without drawing a frame.

The border itself stays, and it is not decoration: against a dark host page
the face measures 1.10:1 (GitHub dark) and 1.04:1 (Tailwind slate-900), so
without it the launcher has no outline there at all.

`backdrop-blur-md` goes too. It sat behind a 95%-opaque fill and bought
almost nothing, while mounting a permanent blur layer over a customer's
page. It did have one real side effect -- promoting the launcher to its own
compositing layer -- and without that the hover scale re-rasterises the mark
every frame and it visibly jitters. `will-change: transform` asks for the
layer directly, and the jitter is gone.

The dot loses its opaque 1.5px collar. That collar was 21% of the dot's
footprint, and because the dot's centre sits *on* the rim, its outer half
painted a hard dark crescent onto the host page rather than onto the
launcher -- which is what read as "heavy". A hairline plus a soft drop
separates it just as well. The fill becomes a lens lit from the upper left,
both stops derived from the signal colour so a new tone needs no new values.

Considered and dropped: tinting the border in the signal colour. On a dark
page the border is the whole silhouette, so tinting it recolours the entire
launcher for a state that can persist for hours -- and the launcher already
pulses in that colour when the failure is new.
2026-08-25 16:42:54 +02:00
Alem Tuzlak 3d23675968 test(web-inspector): type HUD helpers as Node so check-types passes 2026-08-25 15:52:03 +02:00
Lukas Moschitz 4fa38d91f2 refactor(web-inspector): drop the launcher's dead chrome and soften its face
Six Tailwind utilities on the launcher set properties the unlayered `css`
block sets again, and unlayered declarations beat layered ones regardless of
specificity -- so `bg-slate-950/95`, `border-white/20`, `ring-1`,
`ring-white/10` and the two hover variants have never had any effect. Each
was the package's only use, so the checked-in stylesheet drops 939 bytes.
No visual change: verified the three rules are gone from the generated sheet
and the 611 tests still pass.

Kept deliberately: plain `border` (the hand CSS sets only `border-color`),
`rounded-full` (nothing else sets the radius) and the focus-visible trio
(the hand CSS sets only `outline-color`).

The face moves to `#181C1F` at 95%, which review asked for. It is a hair
darker than the `#1C1F24` this replaces -- 17.2:1 against white rather than
16.5:1, a ΔE of 2.3, at the floor of what an eye can separate -- so this
settles the question rather than changing the look.
2026-08-25 15:49:55 +02:00
github-actions[bot] ec8ad0c33a style: auto-fix formatting 2026-08-25 13:30:54 +00:00
Alem Tuzlak 7dd47cc2dd Merge branch 'main' into alem/oss-903-inspector-bubble-emanations 2026-08-25 15:28:55 +02:00
Tyler Slaton c61f0f32ed feat(web-inspector): surface failures on the launcher and open the Inspector on them (#6646) 2026-08-25 06:28:17 -07:00
Alem Tuzlak 3a3df106b5 fix(web-inspector): show Learning on or off in the HUD 2026-08-25 14:53:30 +02:00
Lukas Moschitz 23e3287fa1 test(web-inspector): stop the privacy check tripping over a random id
"no telemetry payload anywhere carries the failure message" serialises the
whole property bag and asserts it does not contain "503". Two of those
properties are the anonymous distinct id -- random hex -- so a three-digit
numeric needle lands inside one roughly once in a few hundred runs. It just
failed a CI job that way, which reads like a privacy breach and is not one.

The ids are excluded by name and asserted to still be strings, rather than
weakening the needles, which are the point of the test. Proven both ways:
with an id seeded to contain "503" the test passes with this change and
fails without it, and it still goes red when a real message is attached to
the payload.
2026-08-25 14:49:17 +02:00
Alem Tuzlak 7d8a3b611e fix(web-inspector): ease HUD details, land Intelligence on Home, add Learning 2026-08-25 14:49:07 +02:00
Alem Tuzlak 303ff4a00e fix(web-inspector): make the whole HUD row open its view 2026-08-25 14:43:07 +02:00
Lukas Moschitz 3ba4a8fbda fix(web-inspector): let the event-error guard narrow a plain string
`refocusEventErrorLanding` reads the subject back out of the card's
`data-cpk-event-error` attribute, where the DOM can only offer
`string | undefined`, and handed that to a guard typed for
`LauncherSignalKey`. Narrowing untrusted input is what the guard is for, so
it takes a string; every caller that already holds a key still satisfies it.

Tests do not typecheck, so the suite stayed green and only `check-types`
saw it -- which failed three CI jobs on the same one line.
2026-08-25 14:37:07 +02:00
Alem Tuzlak afa5e29959 feat(web-inspector): show a hover HUD on the closed launcher 2026-08-25 14:29:51 +02:00
Alem Tuzlak 5391c4886b feat(web-inspector): add view thread in your app (#6562)
## What does this PR do?

Lets a developer open a saved Inspector thread in the live official
chat.

- New header action: **View in your app**
- Official React and Vue chat switch to that thread
- A pinned `threadId` does not block the switch
- **Stop viewing** or an app thread change restores the previous thread
- Example threads have no action
- Production builds hide the action
- Same agent only. No matching official chat shows an error in the
Inspector

Core owns a two-way EventClient bridge
(`@tanstack/devtools-event-client`). The root import is a no-op in
production.

Docs: Inspector guide, section **View a thread in your app**.

## Related PRs and Issues

-
https://linear.app/copilotkit/issue/OSS-871/new-features-add-a-new-view-thread-in-your-app-feature

## Checklist

- [x] I have read the Contribution Guide
- [x] If the PR changes or adds functionality, I have updated the
relevant documentation
- [x] Allow edits by maintainers is checked
2026-08-25 13:52:52 +02:00
Tyler Slaton d7ac976636 fix(web-inspector): preserve independent error signals 2026-08-25 13:46:59 +02:00
Lukas Moschitz cf062dd375 chore(web-inspector): regenerate the stylesheet artifact after the merge 2026-08-25 12:10:52 +02:00
Lukas Moschitz 306ddaa5df Merge remote-tracking branch 'origin/main' into lukas/oss-903-presentation-wire-errors-notifications-to-emanate-from-and
# Conflicts:
#	packages/web-inspector/src/lib/__tests__/telemetry.test.ts
#	packages/web-inspector/src/styles/generated.css
2026-08-25 12:09:52 +02:00
Lukas Moschitz aeac35d168 fix(web-inspector): land an error only on arrival, not on every visit
`handleMenuSelect` applied the event error's landing on every click on Agent
or AG-UI Events. A landing selects the failed agent, clears the event
filters and re-expands the failed row -- help for a reader who came
*because* of that error, vandalism for one who did not.

`lastEventError` outlives being read on purpose, so the how-to-fix card
survives while it is being read. Together that meant: one run error, and
for the rest of the session every return to AG-UI Events silently wiped the
reader's own search filter and re-expanded a row from hours ago. It also
undid the `all-agents` restore eight lines above it, whose comment says it
exists "so the Events tab isn't silently filtered to one agent".

The three arrivals that really are landings keep it -- pressing the
launcher, pressing the card, and an error arriving while its view is
already open. The new test fails with the old call in place, on the value
the reader can see: expected '' to be 'bookFlight'.
2026-08-25 11:26:46 +02:00
Tyler Slaton a5e072301a fix(inspector): restrict defaults to development builds 2026-08-25 11:25:06 +02:00
Alem Tuzlak 328ee3cb58 Merge origin/main into feat/OSS-871-view-thread-in-app 2026-08-24 19:58:19 +02:00
Alem Tuzlak 98968b49c2 fix(web-inspector): address review on event snippets
- Raw recipe: the Events JSON input now calls requestUpdate, so Run and
  Save stop being permanently disabled.
- Tool args recovery: one depth scan replaces the parse-every-prefix loop.
  Truncated args from a streaming tool call now fail at once, not after
  seconds of blocked main thread.
- Chat bookmark: hidden while the tool arguments are incomplete, so a
  partial payload cannot be captured.
- saveEventSnippet: React, Vue, and Angular wrap the body, so a compile or
  storage failure is reported instead of becoming an unhandled rejection.
- Vue and Angular now gate the in-chat affordances on a dev build plus
  localhost, the same as React. showDevConsole: true on a staging URL no
  longer puts a bookmark into a production chat.
2026-08-24 19:10:06 +02:00
Lukas Moschitz aa91d059ef test(web-inspector): cover the Learning error source, and quiet its lint
The `memory` latch shipped with no test at all, and the one test that
mentions it still explained the old behaviour: "the Memory store is never
even subscribed from here". That is no longer true -- opening the view
does subscribe it -- so the test passed while teaching the wrong thing.
The comment now says what actually holds: without an Intelligence context
the store never fetches, so there is nothing to fail.

Three tests: a refused load names itself and lands on Learning, a refused
load arms nothing while the view has never been opened, and a resolved one
clears itself. The middle one is the important one -- it is the constraint
that makes `memory` an unread event rather than a state the launcher could
mirror from a cold start.

The harness needed two things. `intelligence` is now settable, because the
view is a locked teaser without it and the latch is unreachable. And the
memory store stub replaces its state rather than mutating it: the store's
selectors memoize on the state they were handed, so an in-place edit
reports the stale value forever and looks exactly like a signal that never
fires.

Also hoists the selector-escape pattern so `no-control-regex` can be
suppressed on it with a reason -- the control range is the point, since an
attribute selector escapes those too. Lint returns to its 5359 baseline.
2026-08-24 18:16:29 +02:00
Lukas Moschitz 1d1a319b1f docs(web-inspector): record why the wiring latch arms without a window
Arming used to wait 2000ms so a blip produced neither dot nor beat. The
window is gone and that is deliberate, but nothing said so, and the tests
now assert immediate arming as the contract -- so the next reader would
find no trace of the trade-off, only the behaviour.

What the note holds: `threads` can flap and plays a whole gesture for a
blip, bounded by the single pending-beat slot to one gesture per gesture
length; `connection` cannot flap on its own because nothing retries the
handshake; and the `connecting` branch already covers the retry case if a
fix for the mid-session gap adds polling. Mirrored on OSS-904, which is
where such a fix would land.
2026-08-24 18:05:28 +02:00
Lukas Moschitz ebbe70e567 fix(web-inspector): only claim a highlight when there is one to find
The landing card ended in "The failed run event is highlighted below."
unconditionally, but the error carries no guarantee that such an item
exists. A code that reaches `run` through the catch-all without a run of
its own -- a locked thread, an agent that was never registered -- lands on
an empty AG-UI Events with the card still pointing at nothing, and
`applyEventErrorLanding` bails out silently rather than telling it. Same
for a tool error that arrives without a call id.

Split the copy by what it can promise: `advice` is about the reader's next
move and always holds, `highlight` is a claim about this view and renders
only once the item is there. Sending someone to look for something absent
is worse than saying nothing.

The mapping itself is unchanged. Whether a locked thread should be called
a failed run at all is a separate question for its author.
2026-08-24 17:15:41 +02:00
Lukas Moschitz a8bcfebf02 fix(web-inspector): wait for the thread failure before asserting its landing
The thread-list-error route asserts that the first open lands on Threads
rather than Home. That only holds once the list request has been refused,
and the request is still in flight at that point: the helper waits two
microtask turns and a render, never the fetch. On a loaded machine the
fetch loses the race, the launcher carries no signal yet, and the open
lands on Home.

Green locally, red on all six CI matrix combinations. Reproduced here by
delaying the list fetch, which fails the assertion at 300ms and passes it
with this change at 1200ms.
2026-08-24 16:33:23 +02:00
Alem Tuzlak 4bd576d4ea Merge branch 'main' into alem/oss-874-inspector-event-snippets 2026-08-24 15:48:38 +02:00
Alem Tuzlak c226e50259 feat(web-inspector): land run and tool errors on the failed item
Show agent, tool, and error on Inspector banners. Click the launcher to open the failed tool call or RUN_ERROR. Shorten the error pill wait to 400ms. Use dark gray titles on the rose tool-error card so the name stays readable.
2026-08-24 15:12:35 +02:00
Lukas Moschitz 3cd79937cf style(web-inspector): make the pill read as the launcher opening
Five changes from looking at rendered variants side by side, none of them
behavioural.

The reveal animates a rounded clip. Without a round component the
revealing edge is a straight line sweeping sideways, which reads as a
wipe; with it the edge is the capsule's own cap travelling outward.

The text side of the padding is derived from the capsule's radius rather
than being a literal. Padding is measured from the bounding box, but the
first half-height of that side is the rounded cap, so a bare 14px left
the words sitting inside the curve — and the launcher size is itself a
clamp on the viewport, so no literal could have been right at every size.

The label is two lines now, a heading and a subline, at 12px and 10.5px.
It stays exactly as tall as the launcher; there was room above and below.

The pill takes pointer events while it is on screen, so a click opens the
Inspector as pressing the launcher does. That makes the subline's
instruction honest. It is deliberately not focusable: the launcher is
already a focusable control for the same action, and a second tab stop
for one action would be a regression.

The launcher's surface and edge are now two custom properties declared
once on the wrapper and resolved by both the button and the pill, so the
two cannot drift apart. The pill's own red-tinted border is gone — it read
as a second object rather than as the launcher opening. The surface moves
from near-black to a dark grey, softening the edge against a white page
from 20.5:1 to 16.5:1 while staying far above any legibility threshold.

Refs OSS-903
2026-08-22 15:38:16 +02:00
Lukas Moschitz a3c1ff0f6a feat(web-inspector): name the failure on the launcher itself
Once per outage the launcher beats, then opens sideways into a short pill
carrying the failure's name, holds long enough to read, and closes back to
the plain mark. Nothing stays behind: the dot keeps the state, the pill
carries only the moment.

The ticket this belongs to started with a user who could not tell what the
button was. A dot says something is wrong without saying who is asking, so
the pill names both the problem and the control raising it.

Errors only. The announcement feed's preview text is 54 characters against
a 36px launcher, so a pill carrying it would be ten times the width of the
control it grows from, sized by a feed we do not control. The label is read
from the signal rather than keyed off the tone, so a third signal can carry
one by declaring it.

The reveal animates a rectangular clip. Animating width would recompute
layout on every frame of someone else's page; scaling horizontally squashes
the mark itself, not merely the rounded ends. The contract is therefore
restated rather than broken: the rule was never "opacity and transform", it
is that nothing may force a layout per frame.

Direction comes from available room, because the launcher can be dragged to
within a margin of the left edge and that position persists. Left if it
fits, otherwise right, otherwise no pill at all -- the dot and the beat
still fire, so only the label is lost.

Recovery says nothing. The dot going out is already the message, and
announcing it would double the gestures across the break-and-fix cycle that
debugging consists of.

The whole gesture holds the single pending-beat slot, so an announcement
beat waits rather than cutting into it. Reduced motion gets the same words
and the same reading time without the movement. A polite live region speaks
the failure once, so the pill is not a sighted-only feature.

Refs OSS-903
2026-08-22 11:32:48 +02:00