Commit graph

364 commits

Author SHA1 Message Date
Vadym Samoilenko
f91cb16005 fix(middleware): add word boundaries to injection patterns; default role to admin
- Add \b word boundaries to SQL injection and command injection regex patterns
  to prevent false positives on names like "Josh Smith" (sh\s+), "Norm " (rm\s+)
- Change default role in CreateUserModal from 'client' to 'admin'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 09:45:28 +01:00
Vadym Samoilenko
3a2bbc9ca0 fix(membership): correct \$unwind option preserveNullAndEmpty → preserveNullAndEmptyArrays
MongoDB 7.0 rejects the invalid key with code 28811, causing 500 on
GET /organizations/{id}/members.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:58:07 +01:00
Vadym Samoilenko
5f084f359f feat(help): add FAQ tab + full-content search
- New FAQ guide (faq.md) covering: re-render accessible video, stuck jobs,
  linguist/reviewer assignment, downloads, TTS voice, briefs, status colours
- extractSections() parses markdown body text per section; search now
  matches against section body text, not just heading text
- FAQ tab added between Overview and Client in the sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:46:06 +01:00
Vadym Samoilenko
6588feedc7 fix(membership): use 'or ""' to guard against null email/full_name
u.get("key", "") returns None when key exists with null value in MongoDB,
causing Pydantic ValidationError on MemberDetail.email/full_name: str → 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:41:31 +01:00
Vadym Samoilenko
e52abca74b fix(qc): move accessibleCaptionCues memos after state declarations
Resolves TS2448/TS2454 — useMemo blocks referenced captionsVtt and
retimedCaptionsVtt before their useState declarations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:31:58 +01:00
Vadym Samoilenko
90867e9824 fix(qc): show captions on accessible video + allow admin/PM as linguist/reviewer
- Add retimed captions overlay to accessible video player in QCDetail;
  falls back to original captions if retimed VTT not yet generated
- Extend listUsers to accept comma-separated roles (e.g. linguist,admin)
  so admin/production users appear in linguist/reviewer assignment dropdowns

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 18:07:55 +01:00
Vadym Samoilenko
68ac65ac05 fix(briefs): fix Project/Assign-To dropdowns and expand Requested Outputs
Projects:
- PM now sees all active projects (same as admin/production) — was filtering
  to empty when pm_client_ids and org memberships were both unset

Assign To:
- Replaced useOrganizations()+useOrgMembers() with a new GET /admin/brief-assignees
  endpoint accessible to all authenticated users — returns active admin/PM/production
  users sorted by name; shows role next to name in dropdown

Requested Outputs:
- Added SDH Captions (VTT), Descriptive Transcript, Accessible Video (MP4)
- Accessible Video shows Pause Insert / Voice Overlay radio selector
- Added descriptive_transcript field to RequestedOutputs model (backend + frontend)

Access:
- Brief routes now open to 'client' role in addition to admin/PM/production

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:54:21 +01:00
Vadym Samoilenko
a14444d61c Revert "feat(briefs): remove Briefs feature from frontend"
This reverts commit 98ece9faac.
2026-05-01 17:49:12 +01:00
Vadym Samoilenko
98ece9faac feat(briefs): remove Briefs feature from frontend
Deleted route files, App.tsx routes, Sidebar nav item, and Dashboard
"Awaiting Upload" card. The feature wasn't ready (Project/Assign To dropdowns
were empty for non-admin users) and isn't needed at this stage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:48:02 +01:00
Vadym Samoilenko
27286e23db fix(sidebar): show org settings link for platform admins without memberships
Platform admins query GET /organizations (not memberships) so currentOrgId
was always null — hiding the Settings nav link. Now falls back to the first
org from useOrganizations() for admins, gated with enabled:isPlatformAdmin
to avoid 403 for non-admin roles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:45:49 +01:00
Vadym Samoilenko
e60e7c96e7 fix(settings): use organization_id (not slug) in /org/:orgSlug/settings URL
The backend /organizations/{org_id}/members endpoint queries memberships
by organization_id (_id hex string), but the sidebar was building the URL
from organization_slug (e.g. "3m-test"), causing 403 on every Settings page
load ("Failed to load members.").

- Sidebar: derive currentOrgId from organization_id; option values = org ID
- OrgSettingsLayout: alias orgSlug param as orgId for clarity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:34:20 +01:00
Vadym Samoilenko
a3cfe2ff8c fix(renderer): skip ffprobe Phase 3.5 — use pre-computed freeze duration
Cloud Run-generated freeze segments caused FFprobe to return code 1 with
empty stderr when dispatched to the Celery ffmpeg queue, crashing the
render for every language. The freeze segments are created to an exact
pre-computed duration (ad_duration + silence_before + silence_after),
so probing is unnecessary — assign that value directly instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:29:33 +01:00
Vadym Samoilenko
9733700874 fix(captions): remove duplicate native track display, fix position to bottom
- Remove `default` from <track> element so browser doesn't render native
  captions on top of the custom overlay (was causing double display)
- Remove positionTop logic — always render overlay at bottom-14 (above controls)
  regardless of VTT line hints; applies to both VideoWithCaptions and VideoReviewPlayer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:33:33 +01:00
Vadym Samoilenko
df7fec701d fix(ui): connection dot in navbar, profile page, render error visibility + audit log
- Navbar: add WebSocket connection dot (green/yellow/red/gray) from GlobalWebSocketContext
- Profile page: /profile route shows email, full_name, role, auth_provider, languages
- JobResponse: expose failure and error fields (were stored in MongoDB but not returned)
  so frontend now shows actual render error message instead of generic fallback
- render_accessible_video: write JOB_TASK_FAILED audit log entry on render failure
  with language, error detail, step=render
- rerender_accessible_video: same audit log on re-render failure, step=rerender

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:19:12 +01:00
Vadym Samoilenko
2f4925353a feat(pause-insert): adaptive buffer, forward-snap, timeline drag + share link fix
Backend (Phase A):
- A1: Adaptive silence buffer — natural_gap_ms persisted per cue; renderer computes
  per-cue silence_before/silence_after instead of fixed 500ms; per-cue silence files
- A2: Forward-preferred snap — snap_pause_point prefers boundaries up to 4s ahead
  over boundaries within 1.5s behind, reducing mid-scene cuts
- A3: Min-gap validation — pause points with < 200ms gap trigger forward search
  to the next acceptable gap
- natural_gap_ms added to PausePointData model and api.ts type
- New config fields: whisper_snap_forward_window, whisper_snap_backward_window,
  ad_silence_buffer_default, ad_silence_buffer_min_after, ad_min_acceptable_gap
- Tests: test_whisper_snap.py (13 tests), test_video_renderer_buffers.py

Frontend (Phase B):
- B1: Drag pause-point markers — pointer state machine with 3px move threshold,
  clamp to min/max bounds, click-without-move still opens PausePointEditor
- B2: Drag freeze blocks — orange blocks translate with linked pause point
- B3: Time tooltip visible during drag, hidden on release
- Tests: TimelinePreview.drag.test.tsx (10 tests)

Fixes:
- Share link pointed to ai-sandbox.oliver.solutions — added app_url to Settings
  with correct optical-dev.oliver.solutions default; share_url now configurable
  via APP_URL env var
- Removed all ai-sandbox.oliver.solutions references from docker-compose,
  apache config, docs, and scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 16:09:09 +01:00
Vadym Samoilenko
31d631f70d fix(qc-queue): correct sidebar badge and Refresh button loading state
Sidebar My QC Queue badge was showing org-wide pending_qc job count
instead of the current user personal assigned tasks. Now uses
useMyQCQueueCount which sums the linguist and reviewer queue totals
from the same me/language-qc-queue API the queue page uses.

Refresh button now shows a spinner and Refreshing label while the
refetch is in progress so users can see the action took effect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 14:41:37 +01:00
Vadym Samoilenko
8dee0b6ff5 fix(membership): guard against missing/invalid role_in_org in membership docs
list_org_members and _membership_from_doc used bracket access on role_in_org
which raises KeyError if the field is absent (old docs or direct DB inserts).
Also handles ValueError if the stored value doesn't match a valid OrgRole.
Falls back to OrgRole.MEMBER in both cases.

Fixes 500 on GET /organizations/{org_id}/members.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 14:35:09 +01:00
Vadym Samoilenko
997c1f622b fix(rbac): allow reviewer role to assign linguists and reviewers
assign, assign-reviewer, reassign-reviewer, and bulk-assign endpoints
were gated to project_manager/production/admin only, but the Reviewer
QC Detail page exposes Assign buttons to reviewer users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 14:29:15 +01:00
Vadym Samoilenko
d4cb31e5d9 feat(help): add real screenshots for all 7 role guides (77 images)
Captures admin, client, linguist, reviewer, production, project-manager,
and global help screenshots from optical-dev using Playwright MCP.
All markdown-referenced filenames now have corresponding PNG files.
Placeholders used where live data or role permissions prevent full capture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 14:02:54 +01:00
Vadym Samoilenko
2c816a5e69 docs(help): add Timeline Preview & Rendering section to linguist/reviewer/production/admin guides
All 4 roles that access QCDetail now have section explaining:
- Timeline bar colour legend (Video/AD Audio/Queued/Pause Point/Adjusted)
- Render Accessible Video Changes panel triggers and behaviour
- Whisper pause refinement checkbox guidance
- Step-by-step render workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 13:20:48 +01:00
Vadym Samoilenko
ce048a2196 fix(help): resolve screenshot paths under Vite subpath deploy
Markdown guides use /help-screenshots/... (root-relative). With Vite
base=/video-accessibility/, images were requested at the wrong URL.
Custom img renderer now prepends import.meta.env.BASE_URL so paths
resolve correctly on both /video-accessibility/ and local dev.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 13:14:47 +01:00
Vadym Samoilenko
67219797b6 feat(help): add captured screenshots for all 7 role guides (25 images)
Screenshots captured via Playwright against optical-dev. Covers:
global (login + interface), client, linguist, reviewer, production,
project-manager, admin — all 25 PNGs under frontend/public/help-screenshots/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 13:11:40 +01:00
Vadym Samoilenko
6559ccc1f9 feat(help): in-app role-based help guides + screenshot capture pipeline
- Help.tsx: role tabs, TOC scroll-spy, search, lightbox, react-markdown renderer
- 7 markdown guides (global, client, linguist, reviewer, production, PM, admin)
  with explicit click/drag/keyboard annotations throughout
- Sidebar: Help button added at bottom of nav (all roles)
- App.tsx: /help route, no RoleGate
- frontend/public/help-screenshots/{role}/: directories ready for screenshots
- tools/capture-help-screenshots.ts: Playwright screenshot script
  - Clicks "Local login" toggle before filling credentials
  - Uses test-admin local account (not SSO)
- backend/scripts/seed_test_users.py: idempotent MongoDB seed script
  creates 6 local-auth users (admin + 5 roles) for capture + local dev
- .env.screenshots.example: template with test-admin credentials
- Removes docs/video_accessibility_user_guide_v3.md (superseded by in-app guides)
- Deps: react-markdown, remark-gfm, rehype-raw added to frontend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 13:08:13 +01:00
Vadym Samoilenko
d2adfbc3b4 fix(dashboard): briefsData is array, not {briefs:[]} — remove stale .briefs accessor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 12:13:58 +01:00
Vadym Samoilenko
c3a42cb5fe Merge fix/multi-tenancy-and-english-first into main 2026-05-01 12:07:37 +01:00
Vadym Samoilenko
9e6ce657bf fix(schema): empty string → None for captions/AD VTT fields (Bug 2B)
Frontend sends audio_description_vtt: "" for CC-only jobs.
Pydantic validator converts "" to None before validation,
so the backend skips VTT format validation and returns 200
instead of 400.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 12:06:09 +01:00
Vadym Samoilenko
f2968a2989 fix(vtt): regenerate descriptive_transcript.txt after PATCH /vtt saves
Bug 1: Editing any AD cue never updated descriptive_transcript.txt in GCS.
Bug 2A: Uploading replacement CC or AD .vtt had the same root cause.

After saving captions or AD VTT, read the other stream from GCS if not
provided in the request, merge both via generate_descriptive_transcript(),
upload the result to {job_id}/{lang}/descriptive_transcript.txt, and
update lang_output["descriptive_transcript_gcs"] before the DB write.

Bug 2B (CC-only job → 400 on empty audio_description_vtt): already fixed
by the existing `if request.audio_description_vtt:` guard (empty string
and None are both falsy) and frontend `adVtt || undefined` sending no
field rather than an empty string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 12:03:35 +01:00
Vadym Samoilenko
32b12ff0a6 feat(ux): P2 role UX — reviewer queue, dashboard widgets, org filter, WS toast
Phase 2.3: VttEditor sticky banner + Re-translate wired into QCDetail
Phase 3.1: RoleGate on /briefs/* (PM/admin/production only)
Phase 3.2: LinguistQueue — sortable Assigned column, defaultRole prop
Phase 3.3: ReviewerQueue component + /qc/reviewer-queue route + sidebar entry
Phase 3.4: PM dashboard — Overdue and Stuck >24h widgets
Phase 3.5: Production dashboard — Awaiting Upload and Pending QC Handoff widgets
Phase 3.6: Admin UserList — org_id filter dropdown (uses listOrganizations)
WebSocket: onTerminalClose callback + error toast in GlobalWebSocketContext
Runbook: Apache ProxyTimeout ≥60s recommendation for WebSocket keepalives
Backend: fix F841 unused variable in test_cross_tenant_isolation.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 11:58:29 +01:00
Vadym Samoilenko
b427ee9f49 fix(authz): MT-3/6/7/8 org isolation + P1 English-first QC enforcement
Multi-tenancy isolation (P0):
- MT-3: Add get_job_or_403 (org membership check) to all 19+ job action endpoints
- MT-6: Same gate added to all review_notes (5) and vtt_versions (4) handlers
- MT-7: WebSocket /ws/jobs/{job_id} closes with 4403 on org mismatch;
  /ws/jobs passes accessible_org_ids to ConnectionManager; server-side
  keepalive at 20 s (asyncio.wait_for timeout) prevents proxy idle drops
- MT-8: list_users scoped to org memberships for non-platform-admins

WebSocket fixes (Mod Comms 2026-03-18 incident):
- Frontend heartbeat lowered 30 000 → 20 000 ms (was at Apache timeout edge)
- Terminal close codes 4001/4003/4004/4403 no longer trigger reconnect loop
- Silently discard server "keepalive" frames alongside existing "pong"

English-first QC (P1):
- _assert_can_approve blocks target language approval until source is APPROVED
- PRODUCTION/ADMIN roles bypass the gate
- Source VTT edits reset stale APPROVED/PENDING_REVIEW/IN_REVIEW target states

Tests (all passing):
- backend/tests/unit/test_language_qc_english_first.py (15 cases)
- backend/tests/unit/test_routes_jobs_org_isolation.py (12 cases)
- backend/tests/unit/test_review_notes_org_isolation.py (16 parametrized cases)
- backend/tests/unit/test_vtt_versions_org_isolation.py (16 parametrized cases)
- backend/tests/unit/test_websocket_org_isolation.py (11 cases)
- backend/tests/unit/test_admin_users_org_filter.py (7 cases)
- frontend: useJobStatusWebSocket.terminal.test.ts (9 cases)
- frontend: useJobStatusWebSocket.heartbeat.test.ts (9 cases)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 11:43:10 +01:00
Vadym Samoilenko
98764f5065 fix(tts-worker): make concurrency configurable via TTS_WORKER_CONCURRENCY env var
Hardcoded --concurrency=8 with 512MB memory limit caused 1162+ OOM restarts.
Default is 2; set TTS_WORKER_CONCURRENCY in .env.production to override.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 10:22:06 +01:00
Vadym Samoilenko
5d8d992e5a feat(briefs+notify+downloads): fix projects dropdown, add assignee, expand languages, fix PM email, add Download All
- NewBrief: use useAllProjects() (was useProjects('') which never fired)
- NewBrief: expand languages from 12 to 52 options with region variants
- NewBrief: add Assign To dropdown from org members
- Backend: add GET /clients/all-projects endpoint for cross-client project listing
- Backend: add assignee_id to JobBriefCreate/JobBriefResponse models + routes
- notify.py: send completion email to PMs (pm_client_ids) not client user — fixes email never arriving (was looking up users._id by client entity ID)
- Downloads: add Download All button that fetches all files sequentially

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:47:28 +01:00
Vadym Samoilenko
3bed598025 fix(glossary+jobs): add debug logging for glossary failures and fix AllJobs filter stale state
- glossary_service: add step-by-step debug/warning logs at each early-return point so
  the exact failure reason is visible in worker logs (project not found, no active version, etc.)
- glossary_service: guard against source_term_lower=None in ahocorasick automaton build
- glossary_service: guard against target_locale=None in _get_translation
- glossary_service: add full traceback to the outer exception catch for easier debugging
- JobsList: fix statusFilter stale state — useEffect now always syncs with URL params,
  clearing the filter when no ?status= param is present (previously the filter was never
  cleared, so navigating from /jobs?status=X to /jobs kept the old filter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:25:41 +01:00
Vadym Samoilenko
713ae46d4a fix(tts): revert pro TTS to gemini-2.5-pro-preview-tts (3.1 pro TTS doesn't exist yet)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:01:22 +01:00
Vadym Samoilenko
3fb8dce3ee feat(ai): upgrade Gemini models to 3.1-pro-preview and 3.1-pro-tts-preview
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 21:00:32 +01:00
Vadym Samoilenko
12fe4ebcbb feat(tts): upgrade Gemini TTS model to gemini-3.1-flash-tts-preview
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 20:57:37 +01:00
Vadym Samoilenko
43ef3a6cd8 fix(migrations): correct listCollections cursor parsing, add processing_failed+cancelled to status enum
Previous migrations used async-for on a dict (Atlas returns firstBatch, not
async cursor) — silently failed. New migration reads firstBatch correctly and
sets the complete status list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 20:47:21 +01:00
Vadym Samoilenko
8a1440201e fix(migrations): connect to mongo before running migrations in run.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 20:43:48 +01:00
Vadym Samoilenko
99554173e6 feat(migrations): add run.py entry point for python -m app.migrations.run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 20:41:52 +01:00
Vadym Samoilenko
2e8cf8269e fix(tts): fetch job_doc before gcs_path call in _generate_language_tts; add cancelled migration
- translate_and_synthesize.py: fetch job_doc from DB right before the combined
  MP3 upload so gcs_path() has the gcs_prefix needed for newer jobs; removes the
  duplicate fetch that existed later in the same function
- migration_2026-04-30-000001: add 'cancelled' to MongoDB $jsonSchema validator
  enum so cancel_job writes no longer fail Document validation
- Dashboard.tsx: include all active processing statuses in the Processing counter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 20:36:03 +01:00
Vadym Samoilenko
f681bd4f53 feat: add Stop Process button to cancel in-progress jobs
Adds POST /jobs/{id}/cancel endpoint that revokes the Celery task and
sets status to 'cancelled'. Shows a confirmation widget in the job
detail sidebar for admin/production roles when the job is in an active
processing state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 19:50:39 +01:00
Vadym Samoilenko
08a8a0d636 fix(tts): convert lameenc bytearray to bytes before GCS upload
lameenc.encode() returns bytearray, but google-cloud-storage's
_to_bytes() only accepts bytes/str — causing TypeError on every
upload_from_string() call. Cast to bytes() before returning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 19:35:28 +01:00
Vadym Samoilenko
77a9d3b255 fix(docker): add ffmpeg to base image — fixes pydub AudioSegment in worker
ffmpeg was missing from the base image, causing all pydub operations
(AudioSegment.from_file, export) to fail in worker and tts-worker containers.
Moved ffmpeg install from whisper-worker stage to the shared base stage so
all container variants (api, worker, tts-worker, whisper-worker) have it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 19:12:57 +01:00
Vadym Samoilenko
7c15acc18a chore: update poetry.lock after adding lameenc dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 18:34:04 +01:00
Vadym Samoilenko
a53cf960ae fix(tts): replace pydub MP3 export with lameenc (pure Python, no system ffmpeg)
Gemini TTS _pcm_to_mp3 used pydub.AudioSegment.export(format='mp3') which
requires a system ffmpeg binary. Worker containers don't have ffmpeg installed
(video ops run on Cloud Run). Switch to lameenc which is pure Python and
encodes PCM→MP3 without any system binary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 18:24:15 +01:00
Vadym Samoilenko
b0a90777ed fix(ts): cast job.error to string before rendering in failure banner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 18:03:04 +01:00
Vadym Samoilenko
efa2395527 feat: inline title rename in JobDetail and QCDetail
Click the pencil icon next to the job title to rename it inline.
Enter saves, Escape or blur cancels. Available for admin/production/PM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:52:43 +01:00
Vadym Samoilenko
0badae9e5d feat(jobs-list): add per-row Edit (rename) and Delete buttons
- Edit button opens inline rename modal with Enter/Escape support
- Delete button shows confirmation modal with clear warning about
  permanent removal from storage and database
- Both actions available for admin/production/project_manager roles
- Delete uses existing single-job DELETE endpoint (GCS + MongoDB)
- Rename uses existing PATCH endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:49:51 +01:00
Vadym Samoilenko
5db01248b6 fix: pass USE_CELERY_FALLBACK to containers and show real error in failure UI
- docker-compose.yml: add USE_CELERY_FALLBACK env var to api and worker
  services so cloud_run_dispatch uses Celery on optical-dev
- JobDetail.tsx: show actual error message instead of generic
  "Processing failed at ." when failure step is unknown; also show
  job.error string when no structured failure object exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:48:02 +01:00
Vadym Samoilenko
37873c433d fix(deploy): set USE_CELERY_FALLBACK=true on optical-dev — no Cloud Run Jobs here
google.cloud.run_v2 is not installed; optical-dev dispatches pipeline tasks
via local Celery workers, not Cloud Run Jobs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:14:45 +01:00
Vadym Samoilenko
105895dd14 feat: apply EN source VTT changes to all target languages
When a reviewer saves the source language VTT during QC and confirms
the re-translate dialog, all target languages are re-translated via
Celery. Job transitions to `translating` and returns to `pending_qc`
when done. Existing polling in useJob covers progress display.

- schemas/job.py: add `retranslate_languages: bool` to VttUpdateRequest
- audit_log.py: add VTT_RETRANSLATE audit action
- translate_and_synthesize_task: accept languages/retranslate params,
  filter to specified languages, skip video render, return to PENDING_QC
- routes_jobs.py: add _trigger_retranslation helper, call after VTT save
- types/api.ts: add retranslate_languages to VttUpdateRequest
- useJob.ts: invalidate all lang VTTs on retranslate
- QCDetail.tsx: confirmation dialog when saving source VTT with targets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 17:13:06 +01:00