Adds a Schedule tab (Kitsu-style production schedule) under each
project: tasks grouped by type with drag-to-reschedule bars, Day/Week/
Month zoom, manual date-range control, weekend shading, a frozen task
column, and a two-tier month/date axis header. Requires a new
start_date field on Task (start_date was previously missing; only
deadline existed) and shadcn DatePicker inputs replace native date
inputs on the Schedule toolbar and TaskDetailPanel's Start Date/
Deadline fields.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Movie spec checks now also cover codec (h264/h265/mjpeg/dnxhd/dnxhr/
prores/uncompressed/avid/cineform) and frame rate (23.976/24/30/48/60),
detected in-browser via mediainfo.js container parsing - works even
for formats the native <video> element can't decode (MXF, ProRes,
DNxHD). Added .mxf to the supported/allowed format lists.
- Naming pattern supports a raw regex mode (validated by compiling
server-side at save time) as an alternative to the token pattern,
and gained three more tokens: {task_name}, {project_name},
{project_code} (fetched lazily only when referenced).
- Submission Configuration settings page now splits Shot Tasks /
Asset Tasks into separate tabs instead of one merged list; the
per-task-type card was extracted into SubmissionTypeConfigCard.vue
to avoid duplicating it across both tabs.
Coordinators can configure, per task type, accepted file extensions, a
naming convention, and (for video files) required resolution/format/
codec/frame rate. All checking happens entirely client-side in the
browser before upload - the backend only stores and validates the
configuration itself, never inspects submitted files.
- backend: submission_config_by_task_type JSON column on Project,
schemas/submission_config.py for validation, GET/PUT
/projects/{id}/submission-config endpoints. Added .mxf/.ma/.usd(a/c)
to the supported file formats.
- frontend: new Submissions tab in Project Settings
(SubmissionConfigManager.vue) to configure rules per task type.
TaskSubmissions.vue enforces them before upload: extension/naming
checks via regex, and real video resolution/codec/frame rate
detection via mediainfo.js (parses container metadata directly,
so it works for formats browsers can't play natively like MXF,
ProRes, or DNxHD).
- NoteItem.vue: render each note as a rounded card (own notes tinted)
instead of a flat list row.
- TaskNotes.vue: show a "Replying to X" banner with cancel and
auto-focus the composer when Reply is clicked, so replying is
actually visible instead of silently setting hidden state.
- ShotDetailPanel/ShotBrowser/TaskDetailPanel: carry the target note
id through select-task so replying from the shot-level aggregated
notes view pre-fills the reply in the destination task's panel
instead of just switching tabs.
- Textarea.vue: fix v-model, which was never wired up (modelValue/
update:modelValue fell through as dead attrs on the native
textarea), so typed content never reached the bound ref anywhere
it's used - notes, submissions, and shot/asset/episode/project
description fields.
- AssetDetailPanel/ShotDetailPanel/TaskDetailPanel: drop the header
status badge and switch tab labels to icons (with an unread-style
badge on Notes), fixing the TabsTrigger clipping/vertical alignment
it introduced.
DetailPanelOverlay.vue (shared by the shot/asset/task detail panels and
the My Tasks page) used a hardcoded top-[113px] that left a 49px gap
above the panel. The header is h-16 (64px), confirmed via its live
getBoundingClientRect() - switched to top-16 so the panel's top edge
sits flush against it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The sidebar already provides Overview/Shots/Assets/Tasks/Settings
navigation for the current project, making the top ProjectTabs bar
redundant. Removed it from ProjectDetailView.vue and deleted the
now-fully-unused ProjectTabs.vue component.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Split note:edit/note:delete and submission:edit/submission:delete into
four independent permissions each - edit_self/delete_self (acting on
your own note or submission) and edit_other/delete_other (acting on
someone else's). Previously "own" access was an unconditional, unrevokable
ownership check with no permission behind it, and a prior round had
accidentally granted coordinator submission:edit/delete by default
(inconsistent with notes, which were correctly own-only) - both are fixed
here: self-service now goes through a real, default-granted-to-everyone
permission, and acting on someone else's note/submission is an explicit
elevated grant that nobody gets by default.
The Role Management permission editor now shows "Edit Own / Delete Own /
Edit Others' / Delete Others'" as four clear, independently toggleable
options instead of one ambiguous "Edit"/"Delete" checkbox.
migrate_role_permissions.py renames the existing permission rows in place
(rather than leaving orphaned duplicates) and includes a one-time,
idempotent correction that revokes the earlier over-grant from coordinator.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Users can now hold multiple roles, each with its own editable set of
create/edit/delete-style permissions across assets, shots, tasks, task
assignment, review approve/retake, submissions, uploads, and notes
(including internal vs. client note visibility). The 4 existing roles
(coordinator/director/artist/developer) are migrated into the new system
as system roles, seeded to reproduce today's actual behavior exactly;
admins can create custom roles (e.g. "Reviewer", "Outsourcing") via the
new Role Management page and assign multiple roles to a user via a new
"Manage Roles" action on the Team page.
Backend:
- New Role/Permission models and role_permissions/user_roles tables,
plus a one-off, idempotent seed/backfill migration script.
- New require_permission()/user_has_permission() dependency, wired into
the actual mutation endpoints across shots/assets/tasks/reviews,
always preserving existing ownership- and self-service-based access
(e.g. artists editing their own task status, own notes, own uploads,
own submissions) as an unconditional fallback alongside the new
permission checks - nothing that worked before now requires a role.
- New endpoints: PUT/DELETE on task submissions (wires up soft-deletion
columns that existed on the model but were never exposed), plus full
role CRUD and per-user role assignment.
- Along the way: fixed newly-created users not being linked to their
matching system role (silently leaving them with zero permissions),
and unified an inconsistency between the single vs. bulk task status
endpoints that allowed different roles to bulk-update status.
Frontend:
- Role Management page with a grouped, human-readable permission editor
(icons, plain-language action labels, per-resource select-all, live
selected count) replacing an earlier dense matrix prototype.
- hasPermission() added to the existing usePermission() composable
without touching its current isAdmin/isCoordinatorOrAdmin consumers.
- Note composer gets an Internal/Client toggle; submissions gain inline
edit/delete actions gated the same ownership-or-permission way as notes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Inside a specific project, the "Projects" item in the sidebar now shows
Overview/Shots/Assets/Tasks/Settings as sub-links (mirroring the existing
top-of-page ProjectTabs), with the active tab highlighted to match the
current route.
When the sidebar is collapsed to icons, the Projects icon still links
straight to /projects like any other nav item; hovering it reveals the
same 5 sub-links as a flyout menu (a controlled Popover, not a
DropdownMenu, so its own click-to-open behavior doesn't hijack the
icon's normal navigation click).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Task status badges (bulk change-status popover, right-click menu) now
use the real per-project TaskStatusesStore colors/labels instead of the
hardcoded TaskStatus enum, matching EditableTaskStatus everywhere.
- Suppress the native browser context menu from leaking through gaps in
the task table (header/empty space), open popovers/dropdowns and their
submenus, the status Select, and a second right-click at the same
coordinates (the popover's positioning anchor now has
pointer-events: none so it doesn't intercept the repeat click).
- Fix 404s on /api/settings/* caused by a double-declared route prefix.
- Fix 422 on bulk-applying a custom task status by relaxing
BulkStatusUpdate.status from the TaskStatus enum to str, matching the
existing single-task update schema.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
handleContextMenu (and the shift-click range-selection path) resolved the
target row via props.tasks[index], but index is the row's position in the
sorted/rendered row model (table.getRowModel().rows), not the raw prop
array — the two orders rarely match once the default created_at sort is
applied. This caused right-click (and shift-click) to select the wrong row,
corrupting the count shown in TaskBulkActionsMenu. Both call sites now
resolve the task from the actual row data instead of an index lookup into
the unsorted array, matching the pattern ShotsDataTable.vue already used
correctly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Shot/Asset/Task data tables now keep their header row pinned while scrolling
(both the locked two-pane and single-table modes), matching a common data-
table expectation that was missing everywhere.
Brings Asset and Task browsers to full structural parity with Shot's bounded-
height layout (fixed toolbar, internally-scrolling table) instead of their
previous page-scroll model with a sticky-positioned toolbar. AssetsDataTable
gained the synced frozen/movable-pane scrolling it was missing entirely;
TasksDataTable and both browsers/parent views got the same container model.
The global My Tasks page (/tasks) now reuses TaskTableToolbar and
TasksDataTable instead of its own ad hoc filter selects and TaskList,
including the detail panel, bulk status/assignment context menu, and sticky
header for free. Since it spans multiple projects, TaskTableToolbar gained
an optional Project filter (only rendered when a project list is passed in,
so the project-scoped Tasks page is unaffected); episode/assignee filters
populate once a specific project is chosen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The lucide icon was imported as "Activity as ActivityIcon" but the empty-
state template still referenced <Activity>, which was never registered
under that name — triggering a Vue "failed to resolve component" warning
whenever a project's activity feed was empty.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
get_project_activities, get_task_activities, and get_recent_activities were
gating on "project member OR admin", incorrectly blocking coordinators,
directors, and developers from projects they weren't explicitly added to as
members. Every other project-scoped router (shots.py, assets.py) only
restricts the artist role this way — everyone else has access regardless of
membership. Brought activities.py in line with that convention.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes a real N+1 API-call bug (per-cell project-member fetches in shot/asset
task-status cells, now a shared cached store), replaces a polling-based
in-flight request de-dup with promise memoization, adds markRaw() around
icon components in reactive state, and fixes a deep watcher that re-scanned
the whole projects array just to trigger thumbnail loading.
Introduces useAsyncAction and usePermission composables to cut duplicated
store boilerplate and duplicated role/admin checks, applied only where the
existing code was a clean match rather than forced onto everything. Extracts
assets.ts's shared per-asset optimistic-update/rollback helper without
merging the single and bulk API paths, which hit genuinely different
endpoints. Adds toast-on-error for previously-silent secondary loads and
fixes a dead try/catch in the router's auth-init guard along the way.
Also fixes a thumbnail-loading regression introduced earlier in this same
pass: the new ID-keyed watcher never fired on a repeat visit to the Projects
page when the store already held the same project list, so thumbnails
(local component state, reset per mount) silently stopped loading after the
first visit. Replaced the watcher with a direct call after each fetch.
Table virtualization, splitting the two largest view files, broad store
caching, and moving domain types into types/ were scoped out as separate,
higher-risk follow-ups (documented in frontend_tasks.md).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Tasks list embedded assigned_user_name: undefined with a "will be
resolved if needed" comment that was never followed up on, so every task
showed "Unassigned" regardless of actual assignment. Resolve it from the
project members list, matching how the shot table's own assignment control
does it, and show an avatar next to the name. Also swap the hardcoded
status Badge/variant switch for the same TaskStatusBadge + task-statuses
store the shot table uses, so status colors and labels (including custom
per-project statuses) always match between the table and the panel.
Extracts duplication that built up as shot/asset/task features reached
parity: CheckableCommandItem/ColumnToggleList replace 14+ hand-rolled
checkbox-list blocks, shared toolbar pieces (debounced search, detail-panel
toggle, clear-filters, segmented view/context toggle) replace copy-pasted
markup in the three table toolbars, icon-only buttons standardize on the
icon-sm size, TaskBulkActionsMenu's Assign To submenu matches Set Status,
and a shared DetailPanelOverlay/Header/Loading/Error shell backs all three
detail panels (adding a previously-missing error state to the task panel).
Phase 2 of frontend_tasks.md - close feature gaps between domains:
- Asset user-assignment popover, ported from shot's EditableTaskStatus,
wired through columns.ts and AssetBrowser.vue.
- Asset column-locking toggle: two-pane frozen-column layout ported
into AssetsDataTable.vue (adapted for asset's page-scroll layout,
which has no bounded-height container like shot's).
- Task table row-actions menu (View Details + Reassign only - no
Delete/Edit, since no backend support exists for either).
- Real select-task/create-task behavior on asset and shot detail
panels: clicking a task swaps in the actual TaskDetailPanel in
place; "Add Task" opens a task-type picker that creates real tasks
via the existing createAssetTask/createShotTask services.
- create-note/upload-reference/publish-version implemented via a
task-picker that deep-links into TaskDetailPanel's Notes/
Attachments/Submissions tabs (new initialTab prop), reusing the
already-working task-level components instead of building three new
bespoke forms. Also fixed shot's pre-existing dead "Add Note"/
"Upload Reference" buttons the same way.
- Consolidated ProjectMembersManager.vue and ProjectMemberManagement.vue
into one component, combining remove-confirmation and approved-user
filtering with toast feedback and the shared Select/Dialog UI kit.
- Wired ProjectDetailView's "Manage Members" to navigate to the
project's Settings > Team tab.
Also fixed a bug in this session's own new code: TaskBrowser.vue's
row-click handler is a no-op by design, so the new row-actions menu
needed to emit row-double-click (which actually opens the panel)
instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GET /assets/{id} never returned task_details (the schema didn't even
declare the field), unlike GET /shots/{id} which already populated it
correctly. This silently broke the asset detail panel's task list -
surfaced while wiring real task data into it on the frontend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Phase 1 of frontend_tasks.md - stop showing fabricated/broken UI:
- Dashboard now fetches real stats (projects, tasks, users, pending
approvals, API keys, developer stats, pending reviews, admin
activity) instead of hardcoded numbers. Added services/developer.ts
and services/review.ts wrappers for previously-unused backend
endpoints.
- Wired ActivityFeed into every project's Overview page in place of
the "coming soon" placeholder.
- Registered the missing /projects/:id/technical-specs route (view
and service already existed, just unreachable).
- Fixed AssetDeleteConfirmDialog's raw styled divs to use the shared
Alert component and wired it into AssetBrowser, matching
ShotBrowser's impact-summary + type-to-confirm safety pattern
(asset deletion was previously less safe than shot deletion).
- Fixed a shared bug in both delete dialogs where the impact-summary
section never rendered (watch on the open prop needed
{ immediate: true }).
- Replaced native confirm()/alert() with styled AlertDialog/Dialog in
NoteItem, TaskAttachments, and UserMenu's keyboard-shortcuts item.
- Removed dead-end UI: Google OAuth stub buttons, UserMenu items
pointing at non-existent routes, the /developer/docs dead link, and
the no-op action button on the API Keys placeholder page.
- Removed leftover debug console logging across 8 files.
Added frontend_report.md (full audit) and frontend_tasks.md (phased
checklist) as the reference for this and future phases.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
joinedload("reviewer") used a string instead of a class-bound
attribute (rejected by SQLAlchemy 2.x), and models.submission was the
wrong import path for Submission. Both endpoints 500'd whenever
actually called; surfaced while wiring real dashboard/activity data
into the frontend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
When the sidebar is collapsed to icon-rail mode, the Columns/Task
Columns checkbox lists had no icon-only representation. Show icon
buttons that open a dropdown menu with checkbox items instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a lock toggle to the shot table toolbar that freezes the
checkbox, thumbnail, and shot name columns. When locked, the table
splits into a fixed left pane and a horizontally scrollable right
pane so the scrollbar spans only the movable columns. Constrain the
table to fill the viewport height so the scrollbar stays at the
screen bottom, and add min-w-0 to the layout to keep horizontal
overflow inside the table instead of the page.
Also fix the asset Task Status filter to render task type groups by
using the object-aware TaskStatusBadge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove wrapper padding from ProjectAssetsView so toolbar sits flush
under the header, matching the shot page
- Add px-4 to AssetsDataTable root div to match ShotsDataTable margin
- Move thumbnail toggle from left filter group to right action area as
an icon-only button, matching the shot toolbar's right-side pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pass episode_id filter to backend API instead of client-side filtering,
reducing payload size when an episode is selected
- Skip getShot refetch in ShotDetailPanel when initialShot prop is provided
- Fix redundant DB query in list_shots: read project.custom_task_statuses
directly from the already-fetched project object
- Add missing indexes on Task.shot_id, Task.assigned_user_id,
Task.deleted_at and Episode.project_id; add add_perf_indexes.py
migration script to apply them to existing databases
- Center login page layout
- Update CLAUDE.md and AGENTS.md with correct venv path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>