Skip to content

Voice-to-text edit mode (Variant C) + adopt shared @aymurai/ui#89

Open
lionelchamorro wants to merge 267 commits into
mainfrom
feature/vtt-edit-variant-c
Open

Voice-to-text edit mode (Variant C) + adopt shared @aymurai/ui#89
lionelchamorro wants to merge 267 commits into
mainfrom
feature/vtt-edit-variant-c

Conversation

@lionelchamorro

@lionelchamorro lionelchamorro commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch delivers the redesigned voice-to-text flow with the Variant-C side-panel edit mode, and migrates its UI primitives onto the shared @aymurai/ui component library.

The most recent change (this session) does two things:

  • Adopt @aymurai/ui — replaces the in-repo Panda primitives (Button, Card, Switch, Avatar, audio Player, stepper colors) with the shared library and imports its compiled styles.css in main.tsx. SpeakerAvatar is now a thin wrapper preserving the existing speaker prop API.
  • Fixtures refactor — extracts the mock preview chunks into the transcription fixture and adds the Deepgram sample fixture consumed by buildFixture.

Dependency wiring (important for CI)

@aymurai/ui is a private repo (AymurAI/ui-components), consumed as a git dependency over SSH, pinned to the v0.2.1 tag:

"@aymurai/ui": "git+ssh://git@github.com/AymurAI/ui-components.git#v0.2.1"

Because the library's dist/ is gitignored, pnpm builds it on install via the package's prepare script (added in ui-components and released in v0.2.1). This requires:

  • pnpm-workspace.yamlallowBuilds: '@aymurai/ui': true (added here) so pnpm 11 permits the git dep's build scripts.
  • SSH access to AymurAI/ui-components at install time. Local dev with an SSH key works out of the box; CI needs a deploy key / SSH agent with read access to that repo, or pnpm install will fail to clone.

Verification

  • pnpm typecheck — passes (node + web).
  • pnpm knip — passes (warn-only).
  • pnpm install rebuilds @aymurai/ui dist/ (incl. styles.css) via prepare at tag v0.2.1.

🤖 Generated with Claude Code

lionelchamorro and others added 3 commits June 21, 2026 11:47
Dev-only: pre-set api baseURL from VITE_DEV_HOST so APIProtected screens render
under pnpm dev:web (pairs with VITE_USE_MOCK_STT for offline UI work). Inert in
production and when unset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aymurai/ui DialogContent caps max-width at 700px, collapsing the tutorial
grid's text columns. Override width from the consumer (90vw/920px) and replace
the inline style with a Panda css class per panda-css rules.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It is a dev/CI browser-automation CLI with no runtime import; keeping it under
dependencies would ship it with the app. (final-review finding)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lionelchamorro

Copy link
Copy Markdown
Contributor Author

Actualización: alineación con Figma "Speech to text WIP" + arreglos de toolchain

Se agregaron 8 commits (8d6acc5..0763650):

Auditoría visual vs Figma (sección "Speech to text WIP"). Se verificó toda la pantalla a pantalla con Playwright + Chrome sobre pnpm dev:web usando el mock STT integrado (VITE_USE_MOCK_STT=true) y un auto-connect de dev (VITE_DEV_HOST), sin backend. Resultado: Dashboard, Selección (drop/lista), Transcripción (waiting/streaming/completed con "Carga finalizada 100%"), Validación y Modo Edición ya coinciden con Figma — no requirieron cambios.

Único bug encontrado y corregido

  • fix(vtt): widen how-it-works modal so 2x2 grid renders (Figma) (311bff5) — @aymurai/ui DialogContent limita max-width a 700px, lo que colapsaba la grilla 2×2 del tutorial (el texto se cortaba palabra por palabra). Override desde el consumer (width:[90vw!] maxWidth:[920px!], verificado que el CSS generado emite !important) y se reemplazó un style inline por una clase Panda.

Soporte / toolchain

  • chore: restore pnpm toolchain install (e930096) — .npmrc (block-exotic-subdeps=false) + pnpm-workspace.yaml para que pnpm install/dep-check funcione con pnpm ≥ 11 (subdep git de electron-forge).
  • feat(vtt): clear transcriptions on onboarding mount (39e37b0) — acción clearTranscriptions + test.
  • chore(dev): optional VITE_DEV_HOST auto-connect (c862a17) — solo dev, inerte en producción.
  • chore: move agent-browser to devDependencies (0763650).
  • Spec + plan + assets de referencia de Figma en docs/superpowers/.

Gates: 19/19 tests · typecheck (node+web) · biome · knip — todo en verde.

Nota menor: .npmrc usa la clave pnpm block-exotic-subdeps, que genera un warning cosmético de npm durante npm run typecheck (npm la ignora). Se puede mover a pnpm-workspace.yaml si molesta.

lionelchamorro and others added 26 commits June 21, 2026 20:46
Drop .npmrc (its pnpm-only key triggered a cosmetic npm warning on every
`npm run typecheck`); the setting lives in pnpm-workspace.yaml instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Points at AymurAI/ui-components#chore/release-v0.3.0 (commit b11645e); the
v0.3.0 tag is not published yet. Repin to the tag once released. Brings the
ArchiveProgress component (near-square progress bar) + transcript-block/side-panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- process: replace the custom pill-shaped progress bar (border-radius 9999px)
  with @aymurai/ui ArchiveProgress (near-square bar matching Figma); map
  transcription status -> ArchiveProgress states; drop now-orphaned i18n keys
  (label/stop/replace text now rendered by the component).
- transcript editor: read-mode body + title background bg.primary (gray #F6F5F7)
  -> bg.secondary (white) to match the design; edit banner stays gray.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the chore/release-v0.3.0 branch ref with the published v0.3.1 tag.
ArchiveProgress API unchanged; progress bar + screens verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the Figma node spec (get_design_context):
- preview file row: border 2px->4px, padding ->24px, heading->row gap ->32px
- play button: gray bg -> bg.primary-alternative (#E5E8FF), icon 20->28
- file name: paragraph.sm.strong -> paragraph.md.default (16px regular)
- trash icon: 18->24px, text.lighter -> text.default
- drop title: constrain maxWidth so it wraps to 2 lines like Figma

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- edit-mode panel: replace custom TurnSidePanel internals with the library
  SidePanel (pills, timestamp, merge/add/delete), wired to the reducer and
  constrained to a 360px right column. Drops the app-only inline global rename,
  free-form new-person naming, and timestamp-invalid hint (not in the Figma
  design / not in the library API), per product decision.
- read mode: render turns via the library TranscriptBlock (Figma display
  component); edit mode keeps the custom TurnBlock (editable text, search
  highlight, click-to-seek). Read-mode turns keep a ref wrapper so search
  scroll-to-match still works.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The @aymurai/ui Player renders rightSlot flush against the time readout, so the
Finalizar button sat glued to '00:00 / 25:03'. Wrap rightSlot with a left margin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The @aymurai/ui ArchiveProgress always renders a 'Descartar' (✕) button, which
the Figma transcription screen doesn't include. Hide it via a scoped consumer
CSS rule until the library makes it conditional.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend switched the /asr/transcribe/stream SSE format from a
full-snapshot-per-event payload to a discriminated event stream keyed on
`type`. Every event now failed the old schema, so safeParse silently
dropped them all and transcription ended with "no document_id".

- schema/asr: replace ASRStreamEventSchema with a discriminated union over
  meta (document_id, duration), delta (text, progress), segments (document)
  and done (progress).
- transcribeStream: dispatch on event.type — capture document_id from meta,
  drive progress from delta/done (already a 0..1 ratio), build the throwaway
  preview from delta chunks, and take the authoritative paragraphs from the
  single segments event. Drops the old per-event dedup Map and
  current_time/total_time math.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In read mode the @aymurai/ui TranscriptBlock migration dropped two
behaviours: clicking a turn no longer seeked the player, and the playing
turn was no longer kept in view. Restore both — clicking a block seeks
to its startMs (with keyboard activation + aria-label), the active turn
is highlighted, and an effect keyed on activeTurnId scrolls the playing
turn into view as the audio advances while staying out of the way when
paused.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the bottom-to-top marquee animation on the processing screen with
a plain scrollable container. Each new ASR fragment updates the text in
place; the view sticks to the bottom to keep the newest fragment visible
but stops following once the user scrolls up to re-read earlier text.
Drops the inline keyframe, the per-character scroll-duration estimate and
the pending-text swap that the marquee required.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Edit mode was missing the click-to-seek and follow-scroll behavior already
present in read mode. Clicking a turn now seeks the player and selects it
(without re-seeking on later clicks within an already-selected block, so
placing the caret while editing doesn't yank playback back to the block's
start), and auto-scroll pauses while the user is typing in a turn so it
doesn't lose their place if playback moves to another turn meanwhile.

Also fixes SPLIT_TURN duplicating the original [startMs,endMs) range across
all resulting pieces instead of apportioning it — now that split pieces are
independently clickable/seekable, they need distinct time ranges.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… in re-seek guard

Clicking a turn's speaker/timestamp header in edit mode bubbled up to the
block's own click handler, firing onSeekTo/onSelect twice per click. Also,
the "don't re-seek on an already-selected block" guard only checked
isSelected (set by mouse clicks), so tabbing into a block's text via
keyboard and then clicking to place the caret still incorrectly re-seeked
the player, since isSelected was never set for keyboard-only focus.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ace-only

SPLIT_TURN drops a pre/post piece when its trimmed text is empty, but was
still computing the middle piece's boundaries from the raw (untrimmed)
character offsets. When the dropped edge had non-zero width (e.g. a leading
space before the split point), that time span belonged to no turn at all,
so playback silently lost its highlight while passing through it. The
middle piece now absorbs a dropped edge's span instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…imestamp edits

"Agregar turno debajo" inserted a new turn with startMs === endMs — a
zero-width range that useActiveTurn can never match (it requires
startMs <= currentMs < endMs), so a manually inserted turn could never be
highlighted or followed by auto-scroll no matter what timestamp it was
given. The new turn now starts where the active turn ends and fills the
gap to the next turn (capped, or a default duration if there's no next turn).

Also validate manual timestamp edits in the side panel against the
previous/next turns' start times, rejecting values that would break the
turns array's chronological order (e.g. typing 00:30 for a turn sitting
between 00:10 and 00:20) instead of silently applying them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pulled the trailing-media-extension-stripping logic out of asrMapper's
transcriptionTitleFromFile into its own utility, since the upcoming
transcript export feature needs the same behavior (a title carried over
from the source audio's file name shouldn't leak its ".wav"/".mp3"/etc.
suffix into the exported file name). No behavior change for asrMapper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a client-side export module (services/export/) that turns a
transcription into a downloadable file: a shared ExportDocument built once
from the transcription + options, then serialized per format. .txt and
.odt are generated entirely client-side (.odt is a hand-built minimal ODF
package via jszip, including a page footer watermark matching the
anonymizer's own document watermark); .pdf reuses the anonymizer's
existing /convert/odt/pdf backend endpoint by feeding it the client-built
.odt, avoiding a second PDF generator (queries.ts's odtToPdf mutation is
refactored to expose that call as a standalone convertOdtToPdf function).

The Finish screen's single "Descargar .txt" button becomes a format-picker
menu. finish-anonymizer.tsx adopts the new shared downloadBlob helper
in place of its local copy, removing that duplication.

The ExportOptions/ExportDocument split is intentionally kept
format-agnostic and decoupled from any UI, so a future export-preview
screen can reuse buildExportDocument/exportTranscription directly instead
of duplicating this logic.

Co-Authored-By: Claude Sonnet 5 <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.

4 participants