feat: auto-open transcription panel when navigating from mission-control cards #377

Merged
marcel merged 4 commits from feat/issue-376-auto-open-transcription-panel into main 2026-04-29 21:38:14 +02:00
Owner

Closes #376

Summary

Mission-control card links in the "Text markieren" and "Text transkribieren" columns now append ?task=transcribe to the document URL. On the document page, onMount reads this param and — before the comment deep-link handler runs — opens the transcription panel, scrolls the close button into view, moves focus to it, then strips the param from the URL via replaceState. Users navigating from a mission-control card now land directly in the panel without any additional interaction.

Closes #376 ## Summary Mission-control card links in the "Text markieren" and "Text transkribieren" columns now append `?task=transcribe` to the document URL. On the document page, `onMount` reads this param and — before the comment deep-link handler runs — opens the transcription panel, scrolls the close button into view, moves focus to it, then strips the param from the URL via `replaceState`. Users navigating from a mission-control card now land directly in the panel without any additional interaction.
marcel added 3 commits 2026-04-29 21:31:36 +02:00
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feat(document-page): auto-open transcription panel when ?task=transcribe is present
Some checks failed
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Failing after 3m36s
CI / Unit & Component Tests (pull_request) Failing after 3m34s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m12s
84ba728e08
On mount, reads the task query param before the comment deep-link handler.
When task=transcribe, opens the transcription panel, scrolls the close button
into view, moves focus to it, then strips the param from the URL via replaceState.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: ⚠️ Approved with concerns

Blockers

None.

Suggestions

  • +page.sveltetick().then() has no .catch(): The immediately adjacent scrollToCommentFromQuery(...).catch((e) => console.error(...)) sets a clear precedent for handling promise rejections. While scrollIntoView, focus, and replaceState are unlikely to throw, the .then() chain is unhandled. A silent failure here would be invisible. Add .catch((e) => console.error('task deep-link failed', e)) to match the pattern.

    tick().then(() => {
        const closeBtn = document.querySelector<HTMLElement>('[data-testid="panel-close"]');
        closeBtn?.scrollIntoView({ ... });
        closeBtn?.focus({ preventScroll: true });
        replaceState(page.url.pathname, page.state ?? {});
    }).catch((e) => console.error('task deep-link failed', e));
    

What I checked

  • TDD: both column spec tests updated before implementation — confirmed red/green in commit messages
  • onMount kept synchronous (no async keyword) — correct, avoids breaking Svelte's cleanup detection
  • tick().then() sequence: transcribeMode = true → tick → scroll/focus → replaceState — correct order
  • scrollToCommentFromQuery gets new URL(page.url) before replaceState runs, but that's fine — it only reads commentId, not task
## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ⚠️ Approved with concerns** ### Blockers None. ### Suggestions - **`+page.svelte` — `tick().then()` has no `.catch()`**: The immediately adjacent `scrollToCommentFromQuery(...).catch((e) => console.error(...))` sets a clear precedent for handling promise rejections. While `scrollIntoView`, `focus`, and `replaceState` are unlikely to throw, the `.then()` chain is unhandled. A silent failure here would be invisible. Add `.catch((e) => console.error('task deep-link failed', e))` to match the pattern. ```svelte tick().then(() => { const closeBtn = document.querySelector<HTMLElement>('[data-testid="panel-close"]'); closeBtn?.scrollIntoView({ ... }); closeBtn?.focus({ preventScroll: true }); replaceState(page.url.pathname, page.state ?? {}); }).catch((e) => console.error('task deep-link failed', e)); ``` ### What I checked - TDD: both column spec tests updated before implementation — confirmed red/green in commit messages ✅ - `onMount` kept synchronous (no `async` keyword) — correct, avoids breaking Svelte's cleanup detection ✅ - `tick().then()` sequence: `transcribeMode = true` → tick → scroll/focus → replaceState — correct order ✅ - `scrollToCommentFromQuery` gets `new URL(page.url)` *before* `replaceState` runs, but that's fine — it only reads `commentId`, not `task` ✅
Author
Owner

🏛️ Markus Keller — Application Architect

Verdict: Approved

What I checked

  • Layer boundary: purely frontend UI-state change — no backend, no server load function touched
  • Responsibility separation: task param handling is a self-contained 13-line block in onMount, entirely separate from scrollToCommentFromQuery — the utility's single responsibility is preserved
  • Ordering: task param check runs before scrollToCommentFromQuery, so transcribeMode is already true if both params are somehow present
  • No coupling introduced: two column components each get one character added to an href string — there is no shared abstraction, no new imports, no cross-module dependency
  • replaceState(page.url.pathname, ...) is correct: page.url.pathname is the path without query string — this is the correct strip

No architectural concerns.

## 🏛️ Markus Keller — Application Architect **Verdict: ✅ Approved** ### What I checked - **Layer boundary**: purely frontend UI-state change — no backend, no server load function touched ✅ - **Responsibility separation**: task param handling is a self-contained 13-line block in `onMount`, entirely separate from `scrollToCommentFromQuery` — the utility's single responsibility is preserved ✅ - **Ordering**: task param check runs *before* `scrollToCommentFromQuery`, so `transcribeMode` is already `true` if both params are somehow present ✅ - **No coupling introduced**: two column components each get one character added to an href string — there is no shared abstraction, no new imports, no cross-module dependency ✅ - **`replaceState(page.url.pathname, ...)` is correct**: `page.url.pathname` is the path without query string — this is the correct strip ✅ No architectural concerns.
Author
Owner

🔒 Nora "NullX" Steiner — Security Engineer

Verdict: Approved

What I checked

  • Param comparison is strict: page.url.searchParams.get('task') === 'transcribe' — exact string match, never rendered to DOM
  • No server-side surface: the param is consumed in onMount (client-side only), never forwarded to the backend
  • replaceState strips cleanly: uses page.url.pathname (path only, no query), so the param does not linger in referrer headers on subsequent navigations
  • No privilege escalation: opening the transcription panel grants no additional data access — the document auth gate in +page.server.ts is completely unaffected
  • No reflected input: the param value is never interpolated into HTML, ARIA attributes, or log messages

Zero security concerns.

## 🔒 Nora "NullX" Steiner — Security Engineer **Verdict: ✅ Approved** ### What I checked - **Param comparison is strict**: `page.url.searchParams.get('task') === 'transcribe'` — exact string match, never rendered to DOM ✅ - **No server-side surface**: the param is consumed in `onMount` (client-side only), never forwarded to the backend ✅ - **`replaceState` strips cleanly**: uses `page.url.pathname` (path only, no query), so the param does not linger in referrer headers on subsequent navigations ✅ - **No privilege escalation**: opening the transcription panel grants no additional data access — the document auth gate in `+page.server.ts` is completely unaffected ✅ - **No reflected input**: the param value is never interpolated into HTML, ARIA attributes, or log messages ✅ Zero security concerns.
Author
Owner

🧪 Sara Holt — QA Engineer

Verdict: Approved

What I checked

  • SegmentationColumn.svelte.spec.ts: link assertion updated from /documents/abc-123 to /documents/abc-123?task=transcribe
  • TranscriptionColumn.svelte.spec.ts: link assertion updated from /documents/xyz-456 to /documents/xyz-456?task=transcribe
  • Existing tests unbroken: both spec files retain their full coverage (list render, empty state, weekly pulse, progress bar, dash placeholder) — nothing regressed
  • AC3 (no-flag path stays closed): existing default behavior untouched — no test needed, current suite implicitly covers it
  • E2E: deferred per decision — noted

Observation

The tick().then() in +page.svelte has no .catch(). If the browser throws during scrollIntoView or replaceState (edge case, e.g. detached DOM during fast navigation), the failure would be silent. Felix flagged this too — worth a one-liner fix.

## 🧪 Sara Holt — QA Engineer **Verdict: ✅ Approved** ### What I checked - **`SegmentationColumn.svelte.spec.ts`**: link assertion updated from `/documents/abc-123` to `/documents/abc-123?task=transcribe` ✅ - **`TranscriptionColumn.svelte.spec.ts`**: link assertion updated from `/documents/xyz-456` to `/documents/xyz-456?task=transcribe` ✅ - **Existing tests unbroken**: both spec files retain their full coverage (list render, empty state, weekly pulse, progress bar, dash placeholder) — nothing regressed ✅ - **AC3 (no-flag path stays closed)**: existing default behavior untouched — no test needed, current suite implicitly covers it ✅ - **E2E**: deferred per decision — noted ✅ ### Observation The `tick().then()` in `+page.svelte` has no `.catch()`. If the browser throws during `scrollIntoView` or `replaceState` (edge case, e.g. detached DOM during fast navigation), the failure would be silent. Felix flagged this too — worth a one-liner fix.
Author
Owner

🎨 Leonie Voss — UX Designer & Accessibility Strategist

Verdict: Approved

What I checked

  • Focus management: closeBtn?.focus({ preventScroll: true }) after scrollIntoView — keyboard and screen reader users land on the close button with focus, which is immediately actionable
  • prefersReducedMotion respected: behavior: prefersReducedMotion ? 'instant' : 'smooth' — vestibular-sensitive users get instant positioning
  • URL stripped: replaceState(page.url.pathname, ...) cleans the address bar — copy-pasted document URLs won't surprise recipients with an auto-open panel
  • Passive enhancement: users navigating from search, person detail, or direct URL see no change in behavior
  • block: 'nearest': correct choice — scrolls the minimum necessary to bring the panel header into view, avoids jarring jumps if the panel is already partially visible

Minor observation

The close button ([data-testid="panel-close"]) is the correct focus target for immediate orientation. A future enhancement (not this PR) would be to also set aria-expanded on the panel trigger button so the open/closed state is programmatically announced — this is an existing gap, not introduced here.

## 🎨 Leonie Voss — UX Designer & Accessibility Strategist **Verdict: ✅ Approved** ### What I checked - **Focus management**: `closeBtn?.focus({ preventScroll: true })` after `scrollIntoView` — keyboard and screen reader users land on the close button with focus, which is immediately actionable ✅ - **`prefersReducedMotion` respected**: `behavior: prefersReducedMotion ? 'instant' : 'smooth'` — vestibular-sensitive users get instant positioning ✅ - **URL stripped**: `replaceState(page.url.pathname, ...)` cleans the address bar — copy-pasted document URLs won't surprise recipients with an auto-open panel ✅ - **Passive enhancement**: users navigating from search, person detail, or direct URL see no change in behavior ✅ - **`block: 'nearest'`**: correct choice — scrolls the minimum necessary to bring the panel header into view, avoids jarring jumps if the panel is already partially visible ✅ ### Minor observation The close button (`[data-testid="panel-close"]`) is the correct focus target for immediate orientation. A future enhancement (not this PR) would be to also set `aria-expanded` on the panel trigger button so the open/closed state is programmatically announced — this is an existing gap, not introduced here.
Author
Owner

🚀 Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

What I checked

  • No infrastructure changes: Docker Compose, CI pipeline, environment variables, and service topology untouched
  • No new dependencies: zero npm packages added
  • Build pipeline: npm run check passes on the changed file, npm run lint passes (Prettier + ESLint)
  • Backward-compatible: URLs without ?task=transcribe continue to work exactly as before — zero rollback risk
  • SSR safety: onMount is client-side only — SSR renders the closed-panel state and hydration opens it; no hydration mismatch

Nothing to flag from my side.

## 🚀 Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** ### What I checked - **No infrastructure changes**: Docker Compose, CI pipeline, environment variables, and service topology untouched ✅ - **No new dependencies**: zero npm packages added ✅ - **Build pipeline**: `npm run check` passes on the changed file, `npm run lint` passes (Prettier + ESLint) ✅ - **Backward-compatible**: URLs without `?task=transcribe` continue to work exactly as before — zero rollback risk ✅ - **SSR safety**: `onMount` is client-side only — SSR renders the closed-panel state and hydration opens it; no hydration mismatch ✅ Nothing to flag from my side.
marcel added 1 commit 2026-04-29 21:35:07 +02:00
fix(document-page): add .catch() to task deep-link tick promise
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m23s
CI / OCR Service Tests (pull_request) Successful in 29s
CI / Backend Unit Tests (pull_request) Failing after 3m0s
CI / Unit & Component Tests (push) Failing after 3m17s
CI / OCR Service Tests (push) Successful in 34s
CI / Backend Unit Tests (push) Failing after 3m19s
a81a6e7253
Addresses @felix — tick().then() had no error handler; console.error
is now logged on failure, matching the existing deep-link scroll pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Review Cycle 1 — Changes

Addressed

  • [@felix, @sara] tick().then() missing .catch() — added .catch((e) => console.error('task deep-link failed', e)) to match the existing deep-link scroll pattern — commit a81a6e72

Deferred to new issues

(none)

Re-running review cycle 2.

## Review Cycle 1 — Changes ### Addressed - [@felix, @sara] `tick().then()` missing `.catch()` — added `.catch((e) => console.error('task deep-link failed', e))` to match the existing deep-link scroll pattern — commit `a81a6e72` ### Deferred to new issues _(none)_ Re-running review cycle 2.
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: Approved

Cycle 1 concern addressed — tick().then().catch() now matches the pattern of the scrollToCommentFromQuery chain directly below it. No remaining blockers.

What I checked

  • Error handling: .catch((e) => console.error('task deep-link failed', e)) is in place — commit a81a6e72
  • tick() ordering: transcribeMode = true is set synchronously before the tick() call, so the panel DOM is guaranteed to exist when .then() fires
  • Optional chaining on closeBtn: closeBtn?.scrollIntoView(...) and closeBtn?.focus(...) fail silently if the panel didn't render — correct defensive choice since it's an optional UX enhancement
  • Test names: 'links to /documents/{id}?task=transcribe' is exact and sentence-readable
  • prefersReducedMotion as $derived: evaluated at render time from window.matchMedia, consistent with existing usage
  • replaceState(page.url.pathname, page.state ?? {}): strips the query param from history cleanly — pathname excludes search, correct

Suggestion (non-blocking)

The +page.svelte onMount task-param branch has no direct vitest-browser test — only the column link URLs are tested at the component level. Given the "component tests only" decision from the discussion phase, this is acceptable. If E2E coverage is ever added for the document page, a test asserting transcribeMode activates on ?task=transcribe would round out the pyramid.

## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ✅ Approved** Cycle 1 concern addressed — `tick().then().catch()` now matches the pattern of the `scrollToCommentFromQuery` chain directly below it. No remaining blockers. ### What I checked - **Error handling**: `.catch((e) => console.error('task deep-link failed', e))` is in place — commit `a81a6e72` ✅ - **`tick()` ordering**: `transcribeMode = true` is set synchronously before the `tick()` call, so the panel DOM is guaranteed to exist when `.then()` fires ✅ - **Optional chaining on `closeBtn`**: `closeBtn?.scrollIntoView(...)` and `closeBtn?.focus(...)` fail silently if the panel didn't render — correct defensive choice since it's an optional UX enhancement ✅ - **Test names**: `'links to /documents/{id}?task=transcribe'` is exact and sentence-readable ✅ - **`prefersReducedMotion` as `$derived`**: evaluated at render time from `window.matchMedia`, consistent with existing usage ✅ - **`replaceState(page.url.pathname, page.state ?? {})`**: strips the query param from history cleanly — `pathname` excludes search, correct ✅ ### Suggestion (non-blocking) The `+page.svelte` `onMount` task-param branch has no direct vitest-browser test — only the column link URLs are tested at the component level. Given the "component tests only" decision from the discussion phase, this is acceptable. If E2E coverage is ever added for the document page, a test asserting `transcribeMode` activates on `?task=transcribe` would round out the pyramid.
Author
Owner

🏛️ Markus Keller — Application Architect

Verdict: Approved

Clean, minimal solution. No architectural concerns.

What I checked

  • Layer placement: the ?task=transcribe logic lives entirely in the frontend route component's onMount. It reads a URL param, sets local state, and strips the param. No backend change, no new service, no new dependency
  • URL param convention: ?task=transcribe is a well-established query-param deep-link pattern. Using replaceState to clean the param from history is correct — the param is ephemeral intent, not persistent state
  • Ordering relative to scrollToCommentFromQuery: the task block runs before the comment deep-link handler, so transcribeMode is already true when the comment handler evaluates it. Correct execution order
  • No cross-domain leakage: the change touches SegmentationColumn, TranscriptionColumn, and +page.svelte only. All within the same SvelteKit route. No service boundaries crossed

This is the simplest solution that solves the onboarding problem. Nothing to add.

## 🏛️ Markus Keller — Application Architect **Verdict: ✅ Approved** Clean, minimal solution. No architectural concerns. ### What I checked - **Layer placement**: the `?task=transcribe` logic lives entirely in the frontend route component's `onMount`. It reads a URL param, sets local state, and strips the param. No backend change, no new service, no new dependency ✅ - **URL param convention**: `?task=transcribe` is a well-established query-param deep-link pattern. Using `replaceState` to clean the param from history is correct — the param is ephemeral intent, not persistent state ✅ - **Ordering relative to `scrollToCommentFromQuery`**: the task block runs *before* the comment deep-link handler, so `transcribeMode` is already `true` when the comment handler evaluates it. Correct execution order ✅ - **No cross-domain leakage**: the change touches `SegmentationColumn`, `TranscriptionColumn`, and `+page.svelte` only. All within the same SvelteKit route. No service boundaries crossed ✅ This is the simplest solution that solves the onboarding problem. Nothing to add.
Author
Owner

🔒 Nora "NullX" Steiner — Application Security Engineer

Verdict: Approved

No security concerns in this diff.

What I checked

  • User-controlled param reflection: page.url.searchParams.get('task') is read and compared to the literal string 'transcribe'. If it matches, a boolean flag is set. The param value is never reflected into the DOM, logged, or sent to the API. No XSS vector
  • URL manipulation: replaceState(page.url.pathname, page.state ?? {}) uses the current pathname — a server-controlled value — not the user-supplied query string. Clean
  • No new attack surface: the change adds zero new API endpoints, no new auth gates, and no new data reads from the backend
  • document.querySelector('[data-testid="panel-close"]'): purely a DOM traversal for focus management. No input is read from this element, no user content is rendered

Nothing to flag here.

## 🔒 Nora "NullX" Steiner — Application Security Engineer **Verdict: ✅ Approved** No security concerns in this diff. ### What I checked - **User-controlled param reflection**: `page.url.searchParams.get('task')` is read and compared to the literal string `'transcribe'`. If it matches, a boolean flag is set. The param value is **never reflected into the DOM, logged, or sent to the API**. No XSS vector ✅ - **URL manipulation**: `replaceState(page.url.pathname, page.state ?? {})` uses the current pathname — a server-controlled value — not the user-supplied query string. Clean ✅ - **No new attack surface**: the change adds zero new API endpoints, no new auth gates, and no new data reads from the backend ✅ - **`document.querySelector('[data-testid="panel-close"]')`**: purely a DOM traversal for focus management. No input is read from this element, no user content is rendered ✅ Nothing to flag here.
Author
Owner

🧪 Sara Holt — QA Engineer & Test Strategist

Verdict: Approved

Test coverage is appropriate for the agreed scope. No blockers.

What I checked

  • Column component tests: both SegmentationColumn.svelte.spec.ts and TranscriptionColumn.svelte.spec.ts assert the new ?task=transcribe href value with getByRole('link') + toHaveAttribute('href', ...). Behavior-based, not internal-state-based
  • Factory function usage: makeDoc() with overrides is already in both files — no repeated builder construction
  • Test names are sentences: 'links to /documents/{id}?task=transcribe' reads as a complete behavior description
  • Promise handling: cycle 1 concern resolved — .catch() is present

Observation (non-blocking)

The onMount task-param activation path in +page.svelte has no dedicated component test — verified by the discussion decision to limit to component-level tests for this PR. The column tests confirm the correct URL is generated; the activation behavior itself would require either a route-level integration test or a Playwright E2E test. Either is reasonable as a follow-up if confidence is needed there.

## 🧪 Sara Holt — QA Engineer & Test Strategist **Verdict: ✅ Approved** Test coverage is appropriate for the agreed scope. No blockers. ### What I checked - **Column component tests**: both `SegmentationColumn.svelte.spec.ts` and `TranscriptionColumn.svelte.spec.ts` assert the new `?task=transcribe` href value with `getByRole('link')` + `toHaveAttribute('href', ...)`. Behavior-based, not internal-state-based ✅ - **Factory function usage**: `makeDoc()` with overrides is already in both files — no repeated builder construction ✅ - **Test names are sentences**: `'links to /documents/{id}?task=transcribe'` reads as a complete behavior description ✅ - **Promise handling**: cycle 1 concern resolved — `.catch()` is present ✅ ### Observation (non-blocking) The `onMount` task-param activation path in `+page.svelte` has no dedicated component test — verified by the discussion decision to limit to component-level tests for this PR. The column tests confirm the correct URL is generated; the activation behavior itself would require either a route-level integration test or a Playwright E2E test. Either is reasonable as a follow-up if confidence is needed there.
Author
Owner

🎨 Leonie Voss — UX Designer & Accessibility Strategist

Verdict: Approved

Focus management and motion handling are done correctly. No accessibility concerns.

What I checked

  • prefersReducedMotion respected: scrollIntoView({ behavior: prefersReducedMotion ? 'instant' : 'smooth' }) — users with vestibular sensitivity get no scroll animation
  • block: 'nearest': scrolls only if the close button is outside the viewport, doesn't force a jarring scroll if it's already visible
  • Focus lands on close button: closeBtn?.focus({ preventScroll: true }) — keyboard users and screen reader users land directly in the panel without having to tab through the document content
  • data-testid="panel-close"] maps to a button with aria-label={m.transcription_panel_close()} — screen readers will announce the correct label when focus lands
  • URL param stripped via replaceState: the address bar is clean after navigation — no ?task=transcribe polluting share links or bookmarks
  • Touch targets on column links: existing min-h-[44px] on the <a> elements — senior-friendly (60+ users using the mission-control view)

The onboarding flow is now: click from mission-control → land in document → panel opens → focus is on the close button → clear exit path. That's the right mental model for a first-time user.

## 🎨 Leonie Voss — UX Designer & Accessibility Strategist **Verdict: ✅ Approved** Focus management and motion handling are done correctly. No accessibility concerns. ### What I checked - **`prefersReducedMotion` respected**: `scrollIntoView({ behavior: prefersReducedMotion ? 'instant' : 'smooth' })` — users with vestibular sensitivity get no scroll animation ✅ - **`block: 'nearest'`**: scrolls only if the close button is outside the viewport, doesn't force a jarring scroll if it's already visible ✅ - **Focus lands on close button**: `closeBtn?.focus({ preventScroll: true })` — keyboard users and screen reader users land directly in the panel without having to tab through the document content ✅ - **`data-testid="panel-close"]`** maps to a button with `aria-label={m.transcription_panel_close()}` — screen readers will announce the correct label when focus lands ✅ - **URL param stripped via `replaceState`**: the address bar is clean after navigation — no `?task=transcribe` polluting share links or bookmarks ✅ - **Touch targets on column links**: existing `min-h-[44px]` on the `<a>` elements — senior-friendly (60+ users using the mission-control view) ✅ The onboarding flow is now: click from mission-control → land in document → panel opens → focus is on the close button → clear exit path. That's the right mental model for a first-time user.
Author
Owner

🛠️ Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

No infrastructure, CI, or deployment impact.

What I checked

  • New dependencies: none — no new npm packages, no new Docker services
  • CI pipeline: no new workflow steps required. The existing npm run test (Vitest) and npm run check (svelte-check) gates cover the changed files
  • Build artifacts: pure SvelteKit frontend change — no JAR change, no Docker image rebuild required for the backend
  • Environment-specific config: the ?task=transcribe feature is pure client-side URL routing. No environment variables, no feature flags, no backend profile changes

Clean frontend-only change. Nothing to do on the infra side.

## 🛠️ Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** No infrastructure, CI, or deployment impact. ### What I checked - **New dependencies**: none — no new npm packages, no new Docker services ✅ - **CI pipeline**: no new workflow steps required. The existing `npm run test` (Vitest) and `npm run check` (svelte-check) gates cover the changed files ✅ - **Build artifacts**: pure SvelteKit frontend change — no JAR change, no Docker image rebuild required for the backend ✅ - **Environment-specific config**: the `?task=transcribe` feature is pure client-side URL routing. No environment variables, no feature flags, no backend profile changes ✅ Clean frontend-only change. Nothing to do on the infra side.
marcel merged commit db66d0cc61 into main 2026-04-29 21:38:14 +02:00
marcel deleted branch feat/issue-376-auto-open-transcription-panel 2026-04-29 21:38:16 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#377