Skip to content

feat(panel): the first run gets a project screen, the test view gets its log back, and steps join the tree - #71

Open
xevgeniy-ellinskiyx wants to merge 11 commits into
mainfrom
ui-ux-edit
Open

feat(panel): the first run gets a project screen, the test view gets its log back, and steps join the tree#71
xevgeniy-ellinskiyx wants to merge 11 commits into
mainfrom
ui-ux-edit

Conversation

@xevgeniy-ellinskiyx

@xevgeniy-ellinskiyx xevgeniy-ellinskiyx commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

A pass over the panel's first run, the test view, and the annotator. Eight commits, one change each; docs travel with the code they describe.

What was done

  • Project picker screen (First connect lands on an unchosen project — add a project-selection step #11) — a token that resolves to no project now opens a dedicated #view-pick (title, search, list, footer with connected host + Disconnect) instead of the Settings form with a popup over it. projectRowEl() and matchProjects() moved out of core/project-switcher.js for reuse.
  • Connection card verdict — the card shows a pill under the host: green "Connected" or grey "Project not picked."
  • Console & network log fold — the log is its own accordion item above attachments, present on every test.
  • Attachments as cards — thumbnail, name, file type, delete bin; empty lists act as drop targets. New TestomatAPI.deleteAttachment() (v2 API with Web-UI JSON:API fallback).
  • Reported steps use the panel tree (#202) — 20px chevron and status slots, no guide line when folded, errors and logs collapsed into one red column.
  • Annotator overlay reports its state — every exit writes {ready:true} or {error} to storage, so a bail no longer hangs the panel; the panel waits 3s before handing the shot to the editor tab.
  • Real pen and marker cursors — custom cursors with hotspots on the nib; the primary button is now "Save."
  • Editor tools row — a ResizeObserver drops labels instead of letting the row wrap.
  • Project switcher — the list opens without dimming the panel behind it.
  • Panel status — what the panel is doing is a plaque at the bottom, not an inline line.
  • Icons and disclosure heads — new extension icon plus source SVG; a disclosure head carries its own icon with the count next to the label.

Known gap

If the overlay reports {ready:true} and AnnotateCore.create then throws, the panel toasts the error but the promise never settles — left out deliberately to keep the diff reviewable.

xevgeniy-ellinskiyx and others added 11 commits August 25, 2026 11:42
The four PNG sizes the manifest names are re-exported from icon.svg, which
lands beside them so the next resize is not a trace of a bitmap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the label

Run info, Failure, Meta and Steps each get a glyph beside the caret, so a
folded stack of them is told apart at a glance instead of by reading four
words in the same weight. Four new Material paths carry them (info was
already in the set): feedback, data_object, stairs, terminal.

The counter on an accordion head loses its 'margin-left: auto' and takes
the small size: it belongs to the WORD it counts, so the head reads as one
line — 'Steps 25' — instead of a label and a figure at opposite ends of a
wide row. Scoped to a DIRECT child, so a counter deeper in a head keeps
whatever its own rule says.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ecomes Save

The cursor used to be the toolbar's own Material glyph blown up to 28px: a
button icon is drawn to read at rest, so as a cursor it came out thick, and
its nib was nowhere near the pixel the ink actually lands on. The pen and
the highlighter now carry art drawn for the job — a slim silhouette in a
32x32 box with the nib down-left, and a hotspot on the nib itself — inked
in the chosen colour with a halo in the opposite one, so it survives a
white dialog and a dark shot alike.

The primary button says Save, not Apply: the press uploads the shot, and
'Apply' reads as committing the annotations to a canvas the tester would
then still have to send. The shortcut legend follows it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ger hangs the panel

executeScript resolving only says the injected FILES ran. An overlay that
then bailed — no handoff key, no stylesheet, AnnotateCore missing, a throw
inside it — returned silently, and the panel went on awaiting a storage key
nobody would ever write to: 'Annotating…' with the button disabled, for as
long as the panel stayed open.

Every exit of the overlay now writes to that same key: {ready:true} the
moment its host is on the page — before the picture decodes, which is the
slow half — and {error} for each way it can fail, with the reason. Creating
the core is wrapped, so a throw in the page's world is a message instead of
an invisible unhandled rejection.

The panel waits 3s for that verdict before handing the shot to the editor
tab, and asks the page whether the overlay host is actually there before
giving up — a slow page must not end with two annotators open. The dataUrl
is put back under the key on every fallback path, so the tab still finds
the shot.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d says where the run stands (#11)

A token that resolves no project used to land on the full Settings form with
the header switcher's popup opened over it. That put the one thing there is
to do on that screen inside a menu, floating over a panel with nothing behind
it — and the popup is a switcher, which is the wrong verb for a first pick.

#view-pick takes the panel whole, the way the connect screen before it does:
title, search, the list, and a footer holding the connected host with a
tertiary Disconnect — the way back out, since a root view has no Back. The
project strip and the tab row fold away with it; both are scoped by a project
that does not exist yet. Only the list scrolls, so a hundred projects cannot
carry the search box off the top or the footer out of reach.

The rows are NOT a second copy: projectRowEl() and matchProjects() move out of
core/project-switcher.js and both surfaces build and filter through them, so
only the row's skin differs. Picking one is the ordinary switchProject().

With the screen doing the telling, the 'Connected ✓' status line under Save
goes: the Connection card carries the verdict instead, as a pill under the
host — green Connected, or a grey 'Project not picked' with the card's tick
greyed beside it, which is what a half-done first run actually looks like.
disconnectInstance() takes the caller's status line id, since the card's own
sits on a page nobody can reach from the pick screen.

The open switcher also gains a scrim, one layer under the popup: while the
list is up, the panel behind it is out of play.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…list of cards you can delete from

The Status tab held one disclosure called 'Attachments & log' that was four
unrelated things stacked: a hint about the recorder, a Full-page switch, the
console log nested inside it, and two capture buttons on three separate lines.

Console & network log is now its own accordion item, ABOVE Attachments —
reading the log comes before attaching anything. It is there on every test,
not only while recording: a section that appears once you have already found
Rec teaches nobody, so idle it says what the recorder does, how long a window
it keeps and where the chip is. Idle carries no figure on its head — a '0'
beside the name reads as 'recorded, and the page was clean', the opposite of
'nothing has been recorded'. The prearm hint goes with it, and the hovercard's
link points at the fold by its new name.

Attachments keeps the capture controls on ONE row — the two buttons packed
left, Full page pushed right as what it is, a setting of the next shot rather
than an act, with the explanation on an info tip instead of a paragraph. Its
head counts what the result already carries, since the fold is shut by default
and the count is the only thing that says anything landed.

Each attached file is a CARD: a mark (the image's own thumbnail, else a glyph
in the same 56x40 column, so mixed files rule at one x), the name over what
the file is — the panel's own uploads are named by it, so they read as
'screenshot' and 'console & network log' instead of 'JPG' — and a bin.

The bin is new, and so is TestomatAPI.deleteAttachment(): v2 first (the route
the Testomat MCP server calls), falling back to the Web-UI JSON:API on
403/404/405, the same split the upload already lives with. Only the second
failure reaches the caller — a delete no route accepted must not read as done.
It is on every card and disabled with the reason in its place when that row
cannot go: no saved result, a locked run, a degraded session, or a row the
server gave no id to. The confirm is interactive, so the lock is re-asked
after it (#187), and the row is dropped from both merge sources only once the
server has said yes.

An empty list is now a drop target rather than a sentence: it is the one place
the tester is already looking for 'where does the file go', so it answers with
somewhere to put it. Click opens the same picker the button does, a drop rides
the same upload path, and it is gated on the same three reasons — the dropzone
must never invite a drop the upload would then refuse.

An annotated screenshot also lands on the list at once: the server list is
re-read only on reopen, so it used to sit invisible on a screen the tester was
already looking at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The step list had its own geometry — a baseline-aligned row, a 2px quoted bar
for the log, an indent of one --space-3 — so it ruled at none of the columns
the runs tree and the TC studio tree share, and the marks staggered wherever
a step had no chevron to show.

A step block is a tree node now, built from the library's own parts: a 20px
chevron slot every row pays for, leaf or not, a 20px status slot, then the
title. An open subtree is a .tree-children, so it drops the same guide a
folder does and takes the same 28px step in — which means a step folded shut
draws no line at all, because the container the line comes from is hidden.

The rule that closes a block hangs off the step's own half, not off the group,
so a step's error and log stay above the line and its children start below it.
It starts at the status column: the chevron is the control that opens the row,
not part of the line of text being underscored. An error and the log that
explains it close up into one red column instead of two segments with a gap.

treeSlot() moves to run-view.js beside the tree it belongs to, since two
screens want it now.

A raw reporter message also keeps the status tint it was losing: it wears
.code for the mono face and the pre-wrap, and the failed banner now wins over
the code well's grey, so assertion output reads the way the web app shows it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dragged narrow, the test editor's tool strip wrapped onto a second line and
took the body's height with it. It now measures itself — a breakpoint cannot
know which controls are showing — and when the controls no longer sit on one
line the labels go and the icons stand alone, each still named by its tooltip
and an aria-label the short row cannot take away. Measured on the controls'
CENTRES, since the row centres what it holds and the switch is shorter than
the buttons beside it. A ResizeObserver re-fits, guarded on the width the last
fit settled at, and the two things that change the row's width — the Record
button's label growing to 'Stop recording (12)', and the polish button coming
and going — ask for a re-fit themselves.

The row is also re-ordered: Record, Continue, polish and the camera hold its
head, and the AI switch and the template picker — both settings for what those
buttons do — move to its tail behind one auto margin, so the pair travels as a
block and the cap falls back to the picker on rows where the switch is away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#21 landed on main while this branch was open and redrew the very list this
branch had redrawn: it replaced the attachment ROWS with `fileTileItem()`, a
tile shared with the new Artifacts section — one file shape across the panel.
Resolved in favour of that shape, which is newer and used by two lists, and
kept from this branch only what main has no answer for:

- `attTileItem()` — main's tile plus a bin in its corner, a SIBLING of the
  tile because `.file-tile` is itself a <button>. `attRow`/`attNameLink`/
  `attGlyph`/`attMeta` and all their CSS are gone with the card design.
- the dashed dropzone as the empty state, spanning the grid (main's
  `.file-grid > .attachment-empty` already reserved the full row for it).
- the count on the Attachments head, the one-row capture controls, and the
  Console & network log as its own fold — none of which #21 touched.
- `deleteAttachment()` beside main's `presignArtifact()` in api.js.

Main's own decisions are kept as they stand: the Attachments fold opens by
default, the list is `.attachment-list.file-grid`, the per-step lightbox is
gone in favour of the file viewer, and the Artifacts section stays — it only
gains the head icon the rest of the folds got on this branch.

Both docs pages are rewritten to the merged reality rather than left
describing the card list this branch no longer builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d it

The scrim under the open project list darkened the whole panel for a pick
that is one row in a menu, and it dimmed the very rows a tester compares
against — the project they are on is what the list is read against. The
popup already carries its own shadow and its own outside-click close, so
the sheet was doing nothing the menu did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n inline line

Progress rode in the status line under the controls — a line that sits
under the fold on a long screen, and that a job which dies leaves standing
forever: a capture that never answered left "Capturing tab…" printed under
the status buttons with the button disabled and nothing else to read.

It moves to the toast: `progressToast()` is the same plaque with a spinner
and no auto-hide, one slot, at the bottom edge where the panel already
speaks. Capturing, annotating, uploading a file, saving a status, deleting
an attachment, finishing a run and validating a token all go through it.
It comes down on ONE rule rather than at every call site — setStatusLine()
hides it, because a screen printing its own line IS the answer — and the
two flows that end printing nothing (the annotator's Discard, a save whose
tester moved on) take it down themselves. The `Loading …` lines of the list
screens stay lines: they pair with a skeleton, and a toast per navigation
is noise, not information.

And two floors under the capture, so the plaque cannot outlive its job for
a reason the panel never learns: the panel races the worker round trip
against 30 s, and the worker races captureVisibleTab against 8 s — on an
occluded or minimised window Chrome can leave that callback uncalled, and
the timeout drops the flow to the debugger path instead of hanging.

The plaque's own inset is one value on all three sides now (16, not 12 at
the bottom), so it reads as floating over the screen rather than slipped
against its edge.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant