Skip to content

[pages] Paginate My Checks, migrate pages and add a burndown view - #2192

Merged
frankrousseau merged 22 commits into
cgwire:mainfrom
frankrousseau:main
Aug 31, 2026
Merged

[pages] Paginate My Checks, migrate pages and add a burndown view#2192
frankrousseau merged 22 commits into
cgwire:mainfrom
frankrousseau:main

Conversation

@frankrousseau

@frankrousseau frankrousseau commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Problem

  • My Checks loaded the whole task list in one request, which could take very long
  • Many pages (My Checks, All Tasks, Timesheets, Entity Chats, Main Schedule, Budget) were still on the Options API
  • Timesheets had unstable column widths, overly precise durations and no drag scrolling; its side panel ignored Escape
  • All Tasks offered no progress overview and its filter rows were misaligned

Solution

  • Paginate My Checks server-side with upfront filter values, whole-set footer stats and a dedicated ToCheckList component
  • Migrate the listed pages and their close widgets to script setup, fixing bugs surfaced on the way (chat sorting, unassign action, formatDate preferences)
  • Stabilize the timesheets layout, cap durations at one decimal, add grab scrolling and close the person panel on Escape
  • Add a burndown chart view behind a toggle on All Tasks: reusable widget with ideal slope, velocity projection and whole-set totals from a new zou aggregate
  • Align the filter comboboxes on measured values and add real costs to the budget CSV export

Depends on cgwire/zou#1205.

frankrousseau and others added 22 commits August 28, 2026 22:08
Convert the page to script setup: Vuex getters as computeds, actions
through store.dispatch, head() replaced by useHead, sectioned layout
with sorted imports.

Clean pass: the filter chain becomes a single functional predicate,
the duplicated due-date comparator and thenBy tail are factored out,
the three near-identical filter-list builders collapse into one
taskEntities helper, and the arg-ignoring buildSelectionGrid wrapper
is inlined. Dead CSS removed (.push-right, duplicated .data-list).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rework the page on the AllTasks model: filters (production, episode,
task type, feedback status, person, due week) are sent to the
paginated tasks-to-check route, results load page by page with a load
more button, and the header count and footer totals come from the
response stats instead of the loaded subset.

A first request on the new filter-values route restricts every combo
to the values actually present in the pool. The build playlist button
shows a loaded/total indicator with an explanatory tooltip when the
list is partial (new my_checks.build_playlist_loaded_only key,
translated in all locales).

The list itself moves to a dedicated ToCheckList component modeled on
AllTaskList (selection, inline estimation and due date editing, load
more, stats footer) instead of piggybacking on TodosList. Filter
combos are pixel-aligned on a single control height, verified on the
live page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
My Checks now renders through its own ToCheckList component, so the
isToCheck prop and its eleven template branches (assignees column,
hidden description and start date, metadata guard, burned-estimation
highlight) no longer have any consumer. TodosList goes back to being
the plain todos/done list used by the Todos and Person pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert AllTasks and AllTaskList to script setup: getters as
computeds, dispatches, useHead, socket task:update rebound through
socket.on/off, mixins replaced by useFormat and the dom composable.

Clean pass: unused getters, actions, data and methods removed
(nbFrames, getTaskName, getDate, setScrollPosition, dead template
refs, ten dead CSS selectors), the click guard copied from TodosList
that matched nothing in this template dropped, alt-arrow navigation
rewritten with a wrap-around modulo, stats prop default fixed to a
real object, route query read at state init so filtered URLs no
longer trigger a double load, and loadMore only bumps the page on
success.

Two migration-borne fixes: the avatar menu called a missing
onUnassign handler (now dispatches unassignPersonFromTask), and the
statusStats computed would have shadowed the StatusStats component
tag (renamed statusStatsList).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the real costs view is active, the exported CSV now mirrors the
on-screen columns: real cost per past month, real total, estimated so
far and gap, estimated future months, remaining, real + remaining,
estimated total and final gap.

The expense computations (converted expenses, done and remaining
previsional, extended departments) move from BudgetList to Budget so the
export and the table share the same data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6ryRZGB1SLWdwD4wrw8Uj
SquareGrid moves to script setup with typed props. Origami was only
reachable through Spinner's isStylish prop, which no component ever
passes, so the file, the import and the prop are removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Standard script setup conversion. The hardDeleteTextComputed and
hardDeleteLockTextComputed computeds become hardDeleteMessage and
hardDeleteLockMessage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The composable returned the raw lib formatDate while the legacy mixin
wraps it with the dateFormat and use12HourClock getters, so any
migrated consumer would silently lose the user's date preferences.
The reactive bundle now wraps it the same way (the pure named export
stays raw) and exposes use12HourClock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert both components to script setup: socket events rebound
through named on/off handlers, template refs via useTemplateRef,
useHead, and defineExpose on focusMessageBox so the parent ref call
keeps working.

Clean pass on the page: the chat list comparator returned a boolean
(the list was barely sorted), it now orders by newest message first
with a name tiebreaker; selectFirstChat picks the top of the sorted
list; the mounted branch collapses into selectFromQuery which already
falls back. On the chat itself: unused domMixin dropped, dead
add-attachment-modal ref removed, entity prop default fixed to null,
and the undeclared messageToDeleteId instance property becomes an
explicit local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert the page to script setup with the usual transforms and merge
its two inconsistent silent flags into a single guard (the
$options.silent one was never read by the watchers). The mounted
block disappears: the production and studio refs read the route query
at init, which also removes the double fetch on query-carrying URLs,
and loadRoute now syncs the combobox strings so they follow browser
history navigation.

Clean pass: phantom weekString dropped, detail and unit options become
locale-reactive computeds, currentPerson derives from the route, the
filteredPeople do/while turns into a some(), and the router.push
watcher blocks collapse onto the timesheets-<level> route names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The table used auto layout, so column widths were recomputed from the
cell contents at every reload (the tbody unmounts while loading) and
jittered on each month or filter change. Fixed layout sizes them from
the header row once, with overflow hidden on the name column.

Cell durations go through a single cellDuration helper that rounds to
at most two decimals without padding: the hour unit was not rounded at
all (1.6666666666666667 for 100 minutes), the day unit showed padded
zeros, and empty cells in day unit rendered NaN because '-' went
through the hoursToDays division.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The timesheet table can now be dragged to scroll, wired like
PeopleList (grabListMixin plus domMixin and the document move/stop
listeners the mixin expects its host to register).

Side panel: the duration column gets real room (flexible name column
instead of a fixed 300px squeezing it), durations are rounded to one
decimal in both units like the main grid, the nested paddings that
stacked up around the table are removed, and the panel itself
breathes more (1.5em top and sides).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ComboboxStudio deviated on both metrics of the centered header row:
its label takes a 5px padding-top under the field class and its
control renders 38px tall against 42px for its neighbors, so the row
staggered it. Two page-scoped rules pin the label metrics and the
control height, verified pixel-identical on the live page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a window keydown listener to PeopleTimesheetInfo that pushes the
same closeRoute as the close button, keeping the URL in sync at every
detail level. Remove the dead close emit and its unused parent handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert MainSchedule to script setup: getters as computeds, dispatch
calls, useHead, and a scheduleRef template ref for the today scroll.
zoomOptions becomes a computed so labels react to locale changes.

Clean up on the way: drop the unused user getter, the never-mutated
loading/errors flags (the template passes false explicitly since the
Schedule widget defaults isLoading to true), the dead overall-man-days
CSS and the stale header comment. Factor the two item converters
around a shared toScheduleItem helper, inline the single-caller init
and updateRoute bodies, and rewrite expandProductionElement with
async/await.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ComboboxNumber select renders 3em while Bulma fixes its select and
control wrappers at 2.5em, so the flex child was shorter than the date
blocks and the row centering shifted it, roughly compensated by an old
margin-top hack. Pin the three levels to the 40px date-field input
height instead; verified pixel-identical on the live row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert Budget.vue to script setup: pageMixin becomes useHead, the
socket events option becomes socket.on/off in the lifecycle hooks, and
the Vuex maps become computed getters and store.dispatch calls.

Clean up along the way: the computeds no longer mutate the store
entries or each other's results, the eight loading/error wrappers share
a runRequest helper, monthsBetweenProductionDates is derived from the
production instead of reset by hand, the independent mount requests run
in parallel and the duplicate entries request on mount is gone. Dead
state (editBudget flags, department start_date/duration) is removed and
the undeclared budgetEntryToDelete and createBudgetEntry flags are
declared. The hard-delete modal now reads loading.deleteBudget (the
template pointed at a key that never existed).

Budget entries with a null exceptions field are normalised once when
they enter state so BudgetList's in-place exception edits stay
reactive. getMonthCost moves to src/lib/budget.js and replaces the copy
in BudgetPersonRow.

Co-Authored-By: Claude <noreply@anthropic.com>
Add a pressed toggle button at the right of the filter row that swaps
the task list for a burndown chart, persisted in the URL as
view=burndown and reloaded on every filter change. The data comes from
the new open-tasks/burndown zou aggregate so the chart covers the whole
filtered set, not only the loaded pages.

The chart lives in a dedicated reusable BurndownChart widget: remaining
tasks curve extended flat to today, dashed ideal line between the first
start date and the last due date, and an estimations switch that turns
the y axis into remaining man-days. Rendered with the chartkick setup
already used by the budget pages. New burndown i18n keys are translated
in all locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Measured on the live rows: the studio and department combos rendered a
38px control against the 42px of the Bulma selects and the person
multiselect, the status and task-type labels carried a 5px padding-top
the other labels lack (roughly compensated by an old 7px padding hack
on the production combobox) and the status combo added its own 1px
margin-top. Pin the two short combos to 42px, normalize the labels
page-scoped and drop the two stray offsets; verified pixel-identical
on the live rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chart was squashed to 150px, the ideal line never drew and dark
mode kept illegible defaults. Root causes: the page had no definite
height for the flex chain, and chartkick pads every series with nulls
on the merged x axis while its spanGaps default breaks lines there,
which also cut the remaining curve into pieces on filtered views. Fix
the height, span the gaps on all series, and keep the previous chart
mounted during reloads since recreating the canvas mid layout could
stall the first paint.

Visual pass in the Kitsu language: gradient area fill under a thinner
curve, HTML legend chips with the estimations toggle, rounded panel,
theme-pinned canvas colors driven by the isDarkTheme getter, localized
thousands separators from the browser locale like formatDuration, and
a dashed today marker with a light wash over the future zone (small
gated chart.js plugin, inert for the budget charts).

The chart now ends at the schedule end instead of stretching to today
when the production is over, the ideal anchors on the earliest of the
schedule start and first activity and stays flat at zero when the
chart runs past the deadline, and a dashed green projection extends
the curve at the observed average velocity. The list recap moves to a
TasksStatsLine widget shown under both views. New burndown.projection
key translated in all locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert BudgetList.vue to script setup: the dom and grab-list mixins
plus the hand-written document event table become useGrabList, and the
Vuex maps become computed getters. The preference key is a single
computed instead of being rebuilt in two places.

Simplify the list's contract with its rows: the seven bindings repeated
on all five row components collapse into one commonRowProps v-bind,
BudgetDepartmentRow emits toggle-department and takes a boolean
is-collapsed instead of a function prop and the whole map, the
department and person rows read their store maps directly, and the two
dead props on BudgetTotalRow are gone. Person rows are keyed on the
budget entry or person id so expense-only persons no longer share a
null key.

The salary exception edit moves up to Budget.vue, which owns the
entries: it replaces the entry's exceptions object and goes through
runRequest like every other write, which drops the currentBudget prop
and the in-place mutation of computed-derived person objects.

Co-Authored-By: Claude <noreply@anthropic.com>
@frankrousseau frankrousseau changed the title [my-checks] Paginate the page with server-side filters [pages] Paginate My Checks, migrate pages and add a burndown view Aug 30, 2026
@frankrousseau
frankrousseau merged commit 4ba5028 into cgwire:main Aug 31, 2026
5 checks passed
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.

1 participant