Responsive: adapt every screen from 768 to 2560, and fix what manual testing found - #94
Open
lionelchamorro wants to merge 134 commits into
Open
Responsive: adapt every screen from 768 to 2560, and fix what manual testing found#94lionelchamorro wants to merge 134 commits into
lionelchamorro wants to merge 134 commits into
Conversation
The single-document preview redesign removed the last consumers of the selection machinery: file-preview no longer toggles `selected` and the route preview no longer filters unselected files. Remove the now-orphaned action creators, their action types, and the reducer cases they backed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
formatDuration accumulated everything into minutes, so a 2h05m clip read
'125 min. 24 seg.'. Break out an hours component ('1 h.' singular, 'N hs.'
plural) once the duration reaches an hour, keeping the existing minute/second
rendering below that.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The inner Stack wrapped a single TextField, so its gap did nothing — TextField already owns its label-to-box spacing and spans the full column. Render it directly under the form's Stack instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
G1 tickets T1-T3 from the manual responsive testing report (issues 04 and 11):
at 768px SidePanelColumn was `position: absolute` below the `lg` breakpoint, so
it mounted over the content with no space reserved and no way out. Hit-testing
confirmed the "Modo Edición" switch and the "Editar título" pencil were both
unreachable — you could enter edit mode and not leave it.
Stacked (option a) rather than a drawer: a drawer needs Radix's portal,
role=dialog and focus trap, which is DOM structure decided by JS, and the
contract for this work forbids a useBreakpoint hook. Stacking is pure CSS over
the open/close state that already exists in React.
Set de Datos was left alone: `isAnnotable={false}` means labelManagerOpen is
always false there, so SidePanelColumn never mounts visible. What the report saw
as a "lower overlay" is validate-dataset's own gridTemplateRows splitting the
viewport 50/50 in flow — same symptom, different mechanism, separate fix.
New coverage: playwright/hit-test.ts plus side-panel-stacking.spec.tsx assert
reachability, not just widths. The three existing specs that hardcoded the 768px
overlay contract had their expectations updated — an intentional behaviour change
that raises coverage, not a softened gate.
Gates verified green: pnpm validate exit 0, 380 tests / 70 files passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Third adversarial review pass. The G1 fix (e81c4e2) has two halves that only work together: SidePanelColumn became position:static with width:full, maxWidth:[none] and flexShrink:0 below lg, and the two flex parents that hold it switch to flexDirection:column there so a row is actually reserved. side-panel-column.test.tsx pins the panel half exhaustively — seven cases covering position, width, maxWidth, maxHeight, overflow, borders and flexShrink. No vitest test asserted the parent half. Deleting flexDirection={{ base: "column", lg: "row" }} from FileAnnotator's HStack and flattening the editor's `content` to flexDir:"row" left all 380 tests passing, and no type or lint rule can express "this prop must exist", so pnpm validate is blind to it too. Only pnpm test:responsive would have caught it, and neither gate runs it. That mutation is worse than the bug G1 fixed: below lg the panel goes back to being a sibling in a row, but now as a width:100%, flexShrink:0 item, so at 768 it demands the full viewport width next to a document whose container carries minWidth:520px — the pair overflows an overflow:hidden box and the panel is clipped off the right edge, reproducing Issues 04/11 (panel unreachable at 768) with no gate noticing. Both new cases assert flex-d_column + lg:flex-d_row on the panel's parent and, to catch a flat revert, not.toContain("flex-d_row"). Verified both ways: they fail under the mutation above and pass on the restored tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pnpm-workspace.yaml still used the v10-era onlyBuiltDependencies-shaped allowBuilds entry, which broke `pnpm install` on pnpm 11 (the git-hosted @aymurai/ui build script was rejected with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED). Updates the allowlist entry to the commit hash pnpm 11 actually resolves, drops the now-ignored package.json "pnpm" field, and pins packageManager so every machine installs with the same pnpm version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…st guards
Completes G1 (issues 04 and 11 from the manual responsive report) with T4/T5 and
the integrator's repairs from the governance pass.
Set de Datos never went through SidePanelColumn at all — `isAnnotable={false}`
means labelManagerOpen is always false there, so what the report saw as a "lower
overlay" was validate-dataset's own gridTemplateRows splitting the viewport
50/50 in flow. Base tier is now `minmax(0, 62%) minmax(0, 1fr)` so the document
pane keeps usable height at 768; the `lg` tier and every gridTemplateColumns
value are untouched.
Test guards added for the parts the deterministic gates could not see:
- validate-dataset/index.test.tsx asserted only grid-tc_* (columns); it now
guards grid-tr_* too, so reverting the 62/38 split fails a test.
- the CT harness that calibrates criterion 3 omitted the Header, which ate most
of the calibration margin — fixed in the fixture.
Gates: pnpm validate exit 0, 384 tests / 70 files, 48 CT tests across the six
viewports all passing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-app into feature/document-summary
…modal
G2 (issues 02 and 03 from the manual responsive report). Three reported symptoms
turned out to be one root cause: the tutorial cards' 200px non-shrinking image
against `1fr` tracks, which is `minmax(auto, 1fr)`. That also explains the 7px
page overflow on /app/DATA_SET/onboarding — the nodes the report flagged are the
cards.
Corrections to the premise, verified against node_modules/@aymurai/ui/dist:
TutorialGrid does have a collapse breakpoint — `{base: 1fr, md: repeat(2,1fr)}`
— but Panda's `md` is min-width 768px, so 768 is exactly the first width that
does NOT collapse. And the vertical centring lives on the library's card recipe,
not the row, where a root className never reaches.
The override needs specificity, not just a className. main.tsx imports
index.css before @aymurai/ui/styles.css and both declare `@layer utilities`, so
at equal specificity the library wins: a plain gridTemplateColumns override is
inert at 768. Both properties are now pinned behind a self-referencing selector
— (0,2,0), order-independent — with regression tests that fail if the doubled
class or the direct-child selector is removed, because neither is visible to the
deterministic gates otherwise.
TutorialDialog exposes no className, so the modal is recomposed from the
library's own Dialog parts (the same pattern label-manager/merge-dialog.tsx
already uses). No new dependency: @radix-ui/react-dialog is not one, that
wrapper lives inside @aymurai/ui.
.claude/rules/radix-ui.md was documenting ten local `ui/` wrappers of which two
exist; corrected as part of the governance pass.
Gates: pnpm validate exit 0, 390 tests / 70 files, 96 CT tests across the six
viewports.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The governance pass on G2 flagged .claude/rules/radix-ui.md as contract drift and fixed it; CLAUDE.md's pointer to the same claim was outside that finding's scope. Both now say the same thing: Dialog/Popover/Select/Switch/Tooltip come from @aymurai/ui, and components/ui/ holds only scroll-area and back-button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Match transcribeStream's cancellation so Detener reports 'stopped', not 'error'.
Matches ArchiveProgress's stopped state instead of hiding the button.
Matches the transcription export's paragraph gaps, which used to render tight.
It was silently dropped, unlike the ODT/PDF formats and the transcription export.
Dedupes copy that was repeated verbatim in common.ts, unlike the other feature cards.
…lose button
G3 (issues 05 and 06 from the manual responsive report).
The divider was the real defect in issue 05: @aymurai/ui's Toolbar renders the
1px divider in rightSlot's wrapper unconditionally for context='anonimizador'
(verified in the compiled component, not the docblock), so when the row wrapped
at 1024/1366 the separator ended up alone at the start of the second line with
nothing to its left. A separator only means something when it separates two
things on the same line.
Issue 06: the close button was a typographic `X` — a glyph of the Archivo font
inside a <button> — sitting 16px above the tabs' vertical centre while the rest
of the UI uses SVG icons. Now a phosphor <X> with an i18n'd aria-label and a
tooltip, centred with the tabs.
The i18n strings came out of the governance pass, not the original change:
SearchBar had four hardcoded Spanish strings ('Buscar en el documento', 'Aplicar
etiquetas', 'Etiqueta', 'Gestor de etiquetas') even though G3 names that file and
lists the i18next rule as an explicit constraint. They now live in
constants/i18n/locales/es/anonymizer.ts.
Gates: pnpm validate exit 0, 396 tests / 71 files, 108 CT tests across the six
viewports.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G8 (F1, F2, F3 from the manual responsive report), on its own branch. F1 was not what the report said. `AnonymizerExportValidationError` used one hardcoded string — "hay entidades con offsets inválidos, duplicados o solapados" — for all nine issue codes, so the message sent the report chasing the wrong question. A staging run of docs/SIN_ANONIMIZAR.docx (125 paragraphs, full predict → disambiguate) produced 140 `missing_canonical_entity_id` issues out of 187 labels and ZERO offset-shaped ones; the backend only assigns canonical_entity_id to 47, all FECHA. Those same 140 ungrouped labels round-tripped through /anonymizer/anonymize-document as HTTP 200 with a valid 44283-byte ODT, and the field is optional in the backend's own schemas. So treating its absence as fatal contradicted the schema it came from: it is no longer in the fatal list, and the message is now built from the fatal issues actually present. That fix initially reintroduced the same defect silently: before i18n.init(), i18n.t() returns undefined — not the key, as the original docblock claimed — so the message came out blank. Every family now falls back to the locale data module, with regression tests deliberately kept in a file that does not import @/constants/i18n, since that is the condition under test. F2/F3: useAudioSnippet could report `playing` for a file that is not playing when a play() promise settled after the [file] effect tore its element down; both handlers now bail when audioRef.current is no longer their element. The process screens no longer show a progress banner over already-rendered content, or a success banner above a failed row. Verified end-to-end, not by fixture: real app in Chromium against staging, the document driven through all six screens without editing an entity, both downloads intercepted — ODT 44281 bytes (OpenDocument Text), PDF 138978 bytes (20 pages). Written up in tasks/responsive-fixes/G8-e2e-anonymizer-staging.md. Gates: pnpm validate exit 0, 440 tests / 75 files, 108 CT tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G4 (issues 07, 12, 13 plus the 2560px and form-input observations from the manual responsive report). One theme: measurements in px that never grow or shrink, so at 2560 there are hundreds of empty pixels beside an element that is cutting off its own content. - the export error message was clamped to 150px with nowrap + ellipsis and no `title`, while the text needed 512 — the user saw that something failed and could not find out what - the transcription title input was a fixed 361px at every width; with a 108-character title its scrollWidth reached 2211px against 361 visible, and at 2560 there were 1312px free beside it - a 33-character person name overflowed the "Turno seleccionado" card by 24px and put a horizontal scrollbar inside the panel The governance pass found a runtime defect that has nothing to do with widths: EditableTurnText kept stale callback props across memoized rerenders, so a contentEditable commit could run through an outdated handler. Both the critic and the adversary reproduced it independently. Fixed by including the callbacks in the memo comparison and stabilising TurnBlock's commit handler. Gates: pnpm validate exit 0, 401 tests / 71 files, 150 CT tests across the six viewports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ast clear G5 (issues 08 and 09 from the manual responsive report). Issue 08: the transcription block carried its own centred max-width while the search bar and the player ran full-bleed, so the left edges did not line up and the gap changed with the width — 24px out at 1366, 313px out at 2560. The transcription was the one obeying the Figma contract (rule A, min(100% - 96px, 1824px) centred, which lands at x=371 at 2560); the toolbar and player are now in the same reading column instead of the viewport. Issue 09: the toast rendered inside the footer row, overlapping, covering 61% of "Volver" without reserving any space. Footers now publish their measured height as a document-level offset that the toast container clears. The governance pass found a real defect in that mechanism: two Footer instances can coexist during a route view transition, and the unmounting one wiped the global offset the live one still owned — reproduced independently by the critic and the adversary. Footers now publish through a registry, and unmount cleanup recomputes the offset from whatever footers remain. Gates: pnpm validate exit 0, 412 tests / 71 files, 186 CT tests across the six viewports. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G7 T1 (F4 from the manual responsive report). Rapid clicks on "+ Nuevo" created several personas sharing one label — four chips all reading "Persona 4", then three reading "Persona 7" — because `nextPersonaLabel(speakers)` and `SPEAKER_PALETTE[speakers.length]` both read the prop closure, so N clicks that land before a rerender all compute from the same stale list. The fully-formed speaker was then handed to a reducer that only appended it. The derivation now happens inside the reducer, against the state it is about to extend, so the label and colour come from the list as it actually is at that moment rather than as the last render saw it. Landed unverified by the flow: the G7 run hit its 8-hour duration budget during rate-limit backoff, right after this ticket was implemented and before ticket_qa. Committed as a checkpoint because both gates are green — pnpm validate exit 0, 423 tests / 71 files — and the relaunch's reviewers audit the tree including this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G7 (F4 and F5 from the manual responsive report), completing the checkpoint landed in a4e8efb. F4 had a second creation path the first fix did not cover: speaker-picker.tsx's `ensureSpeaker` carried the same race that turn-side-panel.tsx did — it was even documented in a comment as out of scope — so rapid creation there still produced personas sharing a label. Both paths now derive label and colour inside the reducer, against the state being extended. F5 was not only in the panel: asrMapper.ts:57-65 built speaker initials as `P${no}`.slice(...), so every persona from index 10 up rendered the same "P1" badge on the ASR import path, before the UI was ever involved. Initials are now derived so double digits stay distinguishable. Both were found by the governance pass, which reproduced each one rather than reading the diff — the panel fix alone would have shipped looking complete. Gates: pnpm validate exit 0, 440 tests / 72 files, 186 CT tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… errors G6 (issue 10 and the console-noise note from the manual responsive report). __root.tsx defined no notFoundComponent, so any unknown route fell through to TanStack Router's default — the literal string "Not Found", with no layout, no header and no styles. There is now a 404 inside the theme with a link back to /home/features, its copy in i18next like the rest of the UI. The InvalidStateError fired on every step navigation could not be caught by the component tests, since it needs the real router and real transitions. Hence playwright-e2e.config.ts and `pnpm test:e2e:console`, which starts the dev server itself, walks the flow and fails on any console error or pageerror. Deliberately a separate config from the CT suite: different harness, different lifecycle. Verified passing. The governance pass reproduced a horizontal overflow on the new 404 at a real dev server rather than in a fixture; fixed. Gates: pnpm validate exit 0, 446 tests / 75 files, 192 CT tests, and the new e2e console check green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix/pipeline-bugs branched off 8331992 and carried G8 alone: the anonymizer export fix (F1), the audio-snippet stale-play guard, and the process-screen state fixes. Keeping it separate made its diff reviewable, but it also meant F1 was invisible to anyone testing this branch — a QA retest on 2026-08-02 found the export still blocked here, correctly, because the fix lived only there. Merging so a single tree carries every fix from the manual-testing batch.
Two pieces of setup scaffolding that were always meant to be temporary. - pnpm-workspace.yaml: remove the 31 `@tiptap/*@3.29.2` minimumReleaseAge excludes. They were added because those packages were published inside the 24h window and blocked `pnpm install` outright; 3.29.2 is now 5 days old, so the entries are dead weight. Verified: install succeeds without them and the lockfile does not move. - package.json: pin @aymurai/ui to commit 93821cce rather than the mutable `#v0.5.0` tag. That tag already moved once, which desynced the allowBuilds key from the lockfile and broke install for everyone. Resolution is unchanged (same tarball SHA), so this is inert today and removes the failure class going forward. - features.md: drop the console.log bullet for validate-dataset/form-group, which was fixed independently, and refresh the counts it cites. Gates after the change: pnpm test 79 files / 493 tests, pnpm validate exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Issue 01 of docs/responsive-test-2026-07-30/REPORT.md, open since 30/07 because the real fix has to happen in @aymurai/ui: AppHeader centres the stepper with position:absolute on the header root, so it does not participate in the flex row and lands on top of a long featureName. The library ships only dist/ and exposes className on the header root alone, so the two children cannot be reflowed from here. Mitigate by hiding the stepper on narrow windows, which removes the overlap and lets the title render in full. - src/renderer/src/index.css: unlayered media query, hung off the role="list" + aria-label="Progress" markup Stepper renders itself. Semantic markup rather than a hashed aym-* class, so it survives a library rebuild. Unlayered on purpose: Panda emits globalCss into @layer base, while the library's aym-* classes live in @layer utilities — later in the layer order, so a globalCss rule would have lost the cascade regardless of selector specificity. - Breakpoint is xl (1280), not lg (1024). The report called 1024 healthy, but it measured "Anonimizador"; sweeping the longest title, "Resumen de Documento", the overlap only reaches zero at 1048px (12px at 1024, 2px at 1044). 1048 is not used as the cutoff because it leaves no margin for a font fallback or a longer future feature name. - playwright/header-stepper.spec.tsx: 4 tools x 6 widths. Asserts computed display:none below xl, the stepper still visible with 4 badges at and above it, and zero title/badge intersection everywhere. Uses a DOM locator rather than getByRole because display:none drops the element from the accessibility tree, which would make toBeHidden() pass for an element never found. Verified the guard bites: neutralising the media query turns it red at 768 and 1024 for all four tools. Full CT suite 216 passed, pnpm test 493, validate 0. This does not close the issue. It trades an overlap for the loss of the "step N of 4" indicator between 768 and 1279; the compact stepper in the upstream ticket is still the correct fix and cannot be emulated here, since the badge digit comes from the array index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
G10 — the third symptom of issue 05, which G3 left behind. Not a regression: the original report described three symptoms and G3's acceptance criteria only covered two, so the orphaned divider and the two-line label were fixed while the row splitting into two visually unrelated blocks was never measured. It passed five criteria legitimately and left this. At 1024 the search input and the "Aplicar etiquetas" group do not fit on one row, and `ml: auto` + `justifyContent: flex-end` pushed the group to the far right while the search stayed left. The group still wraps, but now starts at the same left edge as the search input (x≈48 for both). G3's prop swap — passing the group as Toolbar's `children` rather than `rightSlot`, which is what removes the library's unconditional divider — is untouched. Guarded in both panel states, which is what the missing criterion needed: the code's own comment notes the wrap point depends on whether the entities panel is open, so a test measuring only the closed state can pass without fixing the real case. The spec asserts the exact control count per state before its geometry loop, so it cannot start measuring nothing. Unrelated bug found by the same pass: AnonymizerLabelSelect fed Radix `value` that goes undefined when no label is selected, flipping it between uncontrolled and controlled. Radix keeps its own uncontrolled value and falls back to it, so re-selecting a label that had been cleared silently no-opped forever for that one option while every other label kept working. Now always controlled. Gates: pnpm validate exit 0, 493 tests / 79 files, 216 CT tests, e2e console check green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…st case The issue-01 mitigation hid the stepper below `xl` (1280px) for every tool. That cutoff was calibrated to the longest title, "Resumen de Documento", which only stops colliding at ~1048px — so the three shorter names lost their progress indicator for hundreds of pixels where they fit perfectly. Visible on a ~1217px window showing "Voz a Texto": zero overlap for every tool, stepper gone anyway, and "Voz a Texto" is the one name that never overlapped at any usable width. Swept in Chromium at 4px steps with the rule neutralised, the width at which the title↔badge intersection first reaches 0: Voz a Texto ~800px → now hidden below 840 Set de Datos ~840px → now hidden below 880 Anonimizador ~880px → now hidden below 920 Resumen de Documento ~1048px → now hidden below 1080 Each cutoff keeps ~40px over its measurement so a font fallback cannot silently reintroduce the overlap. Header emits a `.stepper-hide-below-<n>` class from STEPPER_HIDE_BELOW and index.css carries one mechanical block per value, so the tool→width mapping lives in exactly one place. That map is its own module rather than an export from header.tsx because Playwright CT cannot mount a component whose import statement also carries a named export — it rewrites component imports and then treats Header as declared in the test file. header-stepper.spec.tsx imports the same map, so the two cannot claim different numbers; a value with no matching CSS rule still turns it red, and the per-width overlap assertion still catches a number that is wrong for the real glyphs. 24 checks, 4 tools × 6 widths. This is still a mitigation, not the fix: between 768 and its cutoff each tool trades an overlap for a missing "step N of 4". The compact stepper in tasks/responsive-fixes/ui-components-issue-01.md remains the upstream work. Gates: pnpm validate exit 0, 493 tests / 79 files, 216 CT tests, e2e console check green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @lionelchamorro, your pull request is larger than the review limit of 150000 diff characters
Reviewer's GuideMakes the three main flows (Dataset, Anonymizer, Voice-to-Text) responsive between 768 and 2560 using new ReadingColumn/SidePanelColumn layout primitives and Panda tokens, fixes the side-panel stacking/toolbar alignment/Stepper overlap and audio preview duration bugs, and introduces the Summarizer flow (process/validation/finish) with server/mocked streaming support and summary export while tightening a number of correctness and styling contracts around speakers, validation, and toast/footer behavior. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…eature/responsive-screens # Conflicts: # package.json # pnpm-workspace.yaml # src/renderer/src/components/summarizer/summary-process.test.tsx # src/renderer/src/routes/home/features.tsx
…-screens # Conflicts: # package.json # pnpm-workspace.yaml # src/renderer/src/components/summarizer/document-search-panel.test.tsx # src/renderer/src/components/summarizer/document-search-panel.tsx # src/renderer/src/components/summarizer/summary-finish.tsx # src/renderer/src/components/summarizer/summary-process.test.tsx # src/renderer/src/components/summarizer/summary-process.tsx # src/renderer/src/components/summarizer/summary-validation.test.tsx # src/renderer/src/components/summarizer/summary-validation.tsx # src/renderer/src/constants/i18n/locales/es/summarizer.ts # src/renderer/src/routes/home/features.test.tsx # src/renderer/src/routes/home/features.tsx # src/renderer/src/services/aymurai/asrMapper.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two batches of work on the same branch. 124 commits over
develop.Batch 1 — make the screens responsive
The app was built against a single 1440px Figma frame, and that number was
frozen as
maxWidth: "[1015px]"in the two layouts wrapping almost everyscreen. On a 1920 or 2560 monitor two thirds of the width went unused, and below
1440 the two-panel layouts compressed with no collapse rule at all.
Design published a Pages → Responsive page (15 frames: the three step-3
validation screens, with and without side panel, at 1440/1920/2560). The
measurements came from frame metadata, not from eyeballing screenshots:
flexShrink: 0)min(100% - 96px, 1824px)without a panel,the same formula capped at 1672px for the transcript with a panel, and a
genuinely percentage-based
min(88%, 1520px)for documents with a panelpanel opens
The 1440→2560 upscale needed no media queries:
min()+max-width+flexShrink: 0resolve it declaratively, since the caps are relative to thepane. Breakpoints were added only for the collapse below 1440, which has no
design and is marked as extrapolated.
New foundations: a
desktop(1440px) breakpoint andsizes.content.*/sizes.panel.*tokens inpanda.config.ts, plus two primitives —ReadingColumnandSidePanelColumn— that concentrate the rules so the restof the work is application rather than repetition.
Batch 2 — fix the 18 findings from manual testing
A manual pass across six widths (768 · 1024 · 1366 · 1440 · 1920 · 2560) found
13 responsive defects and 5 functional bugs. All are addressed except one that
cannot be fixed in this repo (below). Highlights, since several turned out not
to be what the report thought:
lginstead of stacking, and at768 the "Modo Edición" toggle and the rename pencil were unreachable — you
could enter edit mode and not leave it. Verified by hit-test, not by eye.
Set de Datos turned out to have a different cause entirely: it never mounts
that panel, so what looked like an overlay was its own grid splitting the
viewport 50/50 in flow.
string used for all nine issue codes — so it sent the report chasing offsets
when the real cause was
missing_canonical_entity_id. A staging run produced140 of those out of 187 labels and zero offset-shaped ones, and those same 140
round-tripped to a valid ODT. The field is optional in the backend's own
schemas, so treating its absence as fatal contradicted the schema it came
from.
200px non-shrinking image against
1frtracks, which isminmax(auto, 1fr).@aymurai/uistyle needs deliberate specificity:main.tsximports
index.cssbefore the library's stylesheet and both declare@layer utilities, so at equal specificity the library wins and a plainclassNameoverride is inert.Fixes for things found while fixing the above, not in the original report: a
blank export error message, a
contentEditablecommit running through a stalehandler, a footer wiping the toast offset during route transitions, and a Radix
Select flipping between controlled and uncontrolled so re-selecting a cleared
label silently no-opped forever.
Verification
pnpm validate— biome +tsc --noEmitpnpm test— 493 tests / 79 files (380 / 70 before this work)pnpm test:responsive— 216 component tests across the six widths (18 before)pnpm test:e2e:console— new; drives the real app and fails on any console errorThe component tests assert numbers, not pixels: panel widths, column caps,
horizontal-overflow detection, hit-testability, and zero vertical skew between
sibling cards. Comparison against the Figma frames is visual and manual — the
frames mock browser chrome and use different data, so a pixel diff would be all
false positives.
Still open
in
@aymurai/ui, which ships onlydist/and exposesclassNameon theAppHeader root alone, while the collision is between two of its internal
children. Mitigated here by hiding the stepper below a per-tool, measured
cutoff (840/880/920/1080 by title length) — which trades the overlap for a
missing "step N of 4" and is not the fix. The upstream work is a compact
stepper that keeps the wayfinding.
useEntityGroupsskips mentions without a
canonical_entity_id, which is the normal case, nota pending state — 140 of 187 in the test document. The user reviews 47 and 187
get anonymized. Needs a UX decision before code, so it is deliberately not in
this PR.
isDismissedflag drives both the success and the error callout onthe process screens, so dismissing one pre-dismisses the other.
Note for reviewers
The planning documents, per-issue contracts and staging evidence for this work
live under
tasks/anddocs/responsive-test-2026-07-30/, both gitignored, sothey are not in this diff. Ask if you want any of it moved into the repo.
🤖 Generated with Claude Code
Summary by Sourcery
Introduce summarizer feature and responsive layout primitives, fix multiple UX and data-handling bugs in anonymizer and voice-to-text flows, and complete the migration from Stitches to Panda CSS with new tests and tooling.
New Features:
Bug Fixes:
Enhancements:
Build:
Documentation:
Tests:
Chores: