Skip to content

feat(ui): time tracking, timer chip, quick start and status settings (M5) - #4

Closed
gdarko wants to merge 7 commits into
feat/ui-boardfrom
feat/ui-time
Closed

gdarko wants to merge 7 commits into
feat/ui-boardfrom
feat/ui-time

Conversation

@gdarko

@gdarko gdarko commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

M5, the time-tracking UI, on top of feat/ui-projects. Backend unchanged: no
PHP file is touched and composer run test is still 229 green.

What lands

  • Timer store (stores/timer.ts). A plain reactive singleton, because a
    module bundle has no Pinia. The elapsed time is recomputed from started_at
    on every tick rather than counted up, so a throttled tab, a sleeping laptop
    and a clock correction all land on the right number at the next tick. A 409
    from timer/start means another tab got there first: it is reported and the
    real running entry is read back.
  • Header chip (components/TimerChip.vue), through registerHeaderAction
    at priority 30, visible only while an entry runs. Task name, h:mm:ss, and a
    stop button; the name opens the timesheet.
  • Quick start launcher (components/QuickStartOverlay.vue), through
    registerCompanyLayoutOverlay. A floating button above where a chat bubble
    would sit, a task search, a description, and stop/discard while running.
    Hidden in platform administration.
  • Timesheet (pages/TimePage.vue) at
    /admin/modules/tasks-projects/time, meta.ability
    tasks-projects:view-own-time. A "My time" week grid with per-day and week
    totals, honouring the week_start setting, and an "All time" tab with a
    server-side table filtered by member, project, date range and billing.
  • Entry editor (components/TimeEntryModal.vue). A searchable task picker,
    a date, either a duration (1:30 or 1.5) or a start and an end, a
    description and a billable switch. Invoiced entries are shown read-only with
    the reason; 422 messages land under their fields.
  • Settings page, through registerCompanySettingsPage at path
    tasks-projects. It links to the host's generic module settings form for the
    four scalar settings and owns the task status editor: colours, names, the
    default and closed flags, add, delete and up/down reorder.
  • i18n for every string, in messages/time.ts.

Decisions worth reviewing

  • The current user id comes from GET /api/v1/bootstrap,
    current_user.id.
    The module cannot read the host user store, and the time
    endpoints only narrow to "my time" when asked for a user_id. It is read
    once per company session and kept in stores/session.ts.
  • The "All time" tab appears when the settings say members_see_all_time, or
    when a probe for GET time-entries without a user_id filter returns
    someone else's row.
    The API already narrows the list for a caller who may
    not see more, so this cannot offer a tab that would come back empty. It can
    under-offer: a manager in a company where nobody else has logged time yet
    sees only "My time" until someone does.
  • The scalar settings are not mirrored. The host renders them from the
    module's PHP settings schema at /admin/settings/modules, so the settings
    page links there rather than giving a company two places to write the same
    value. There is no deep link to one module's form in the host router today,
    only the modules index that opens the settings modal.
  • A duration-only entry is stamped at 09:00 local, not midnight, which
    would land on the previous day for anyone east of UTC. Both timestamps and
    the duration are sent, so the server's rounding still decides the stored
    minutes.
  • Reorder is up/down, not drag. It calls the same
    POST task-statuses/reorder a drag would, needs no dependency, and is
    reachable from a keyboard.
  • Task names are cached client-side (stores/tasks.ts), because
    TimeEntryResource carries task_id and nothing else. Unknown ids are
    fetched five at a time and a task that cannot be read keeps its id as its
    label.

Shared files

init.ts gains one import and one call; everything else lives under
registrations/time.ts, api/time.ts, messages/time.ts and new files, to
keep the merge with the board branch small. types/task-status.ts is the
minimal shape both slices need.

Checks

pnpm run lint, pnpm exec tsc --noEmit, pnpm run build (one
window.__invoiceshelf_vue destructuring, no leftover from "vue"),
composer run lint, composer run test (229 passing), and both module
validators. Not verified in a browser from this worktree: the running stack
serves the sibling checkout.

https://claude.ai/code/session_01DCf36XDKprZifej8dc2r1E

The time screens need three things the projects slice does not: a client for
the entry, timer and status endpoints, somewhere to keep the running clock,
and the session facts a module bundle cannot read from the host stores.

The timer is a plain reactive singleton because a module has no Pinia. It
recomputes the elapsed seconds from `started_at` on every tick rather than
counting up, so a throttled tab, a sleeping laptop and a clock correction all
land on the right number at the next tick, and a 409 from `timer/start` is
reported and followed by a read rather than treated as a failure.

Task names live in their own cache: `TimeEntryResource` carries `task_id` and
nothing else, so every timesheet row, the header chip and the entry editor
would otherwise show a bare number.
Six small components rather than two large ones, so a failure in any of them
leaves the rest of the page standing: the header chip, the floating launcher,
the week grid, the managers' table, the entry editor and the status editor.

The editor accepts the two ways people describe the same work, a duration or
a start and an end, and writes both onto the entry; a duration-only entry is
stamped at 09:00 local rather than at midnight, which would land on the
previous day for anyone east of UTC. An invoiced entry is shown and never
offered for editing, because the money on it belongs to the invoice.

The launcher sits above where a chat bubble would be, and hides itself in
platform administration, where no company is active.
Everything the slice contributes to the host is declared in one file, so
`init.ts` only gains a line: the timesheet page under the module namespace,
the header chip, the company layout overlay, the company settings page and
the lifecycle wiring.

Nothing reads from the network inside the boot callback, because Pinia is not
installed yet when it runs; the first read waits for `bootstrap:completed`,
and a company switch clears the previous company's timer, task names and
settings before asking again.

The settings page links to the host's generic module settings form for the
four scalar settings rather than mirroring it, which would give a company two
places to write the same value, and owns the task status editor, which has no
host equivalent. The API still enforces `manage-task-status`, so a member who
may not use it is told so instead of seeing a form that refuses to save.
The package ships compiled, so `dist/` is committed with the source change and
installs without a build step on the target system. It will be rebuilt when
this branch merges with the board work.
`dayOf` and `taskName` were written for callers that ended up using the
labelled variants instead. Dead code in a shipped bundle is weight the browser
downloads for nothing.
…tracking branch

Keeps both registrations in init.ts, takes the richer TaskStatus shape
from the board side plus the time side's input type, the time side's
fuller TimeEntry types, and rebuilds dist/ from the union.
@gdarko
gdarko changed the base branch from feat/ui-projects to feat/ui-board September 14, 2026 21:48
Registry::registerMenu accepts a priority (lower sorts first inside the
group, default 100); the entry now sets 10 so Projects precedes modules
that keep the default.
@gdarko

gdarko commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #14, which carries this stack consolidated into three commits on top of main.

@gdarko gdarko closed this Sep 16, 2026
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