Skip to content

Mobile redesign, consistent UI, and sync/data-loss fixes - #17

Merged
Tricked-dev merged 78 commits into
Tricked-dev:masterfrom
AKolenda:mobile-redesign
Sep 27, 2026
Merged

Tricked-dev merged 78 commits into
Tricked-dev:masterfrom
AKolenda:mobile-redesign

Conversation

@AKolenda

@AKolenda AKolenda commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This moves SolidVerdant to a new mobile layout and makes the whole UI follow one design language. It also includes a bug audit and a performance pass across the app.

Layout and navigation

  • A side menu with Time Tracker, Calendar, Reports and Settings. Review opens from Settings.
  • The running timer is docked at the bottom of Time Tracker. Swiping it up (or tapping it) opens the entry's details with a live clock and Pause/Stop.
  • Time Tracker history uses week and day headers and stacked entry cards. Swipe right continues an entry, swipe left deletes it after a prompt, and review checks appear on the card itself.
  • Each entry's play button (and Continue in Calendar) works while a timer runs: it stops the running timer and starts that entry in one step, so switching jobs is one tap. A paused timer is ended the same way.
  • Calendar opens on the Day view with a week strip. It has swipe paging, hold-to-drag (smooth, with the drop snapped to the slot), and a tap opens entry actions (Continue, Edit, Duplicate, Split, Delete). Entries keep their project colour: a tinted card with a colour bar, as before.
  • In Reports, tapping a job in Estimates & progress lists every entry the job's spent total counts: all dates, and everyone's time with their names when the account may see it. An account that may only see its own time gets its own entries and a one-line note.
  • Reports entry lists opened while the range is still loading fill in when the server answers; they used to keep only the entries cached on the phone.

One design language. Every sheet, list and dialog now uses the grouped style, built from shared components:

  • AppSheet bottom sheets: a bold title, grouped sections and a Done button.
  • ConfirmDialog, red for destructive actions.
  • AppTimePickerDialog, which follows the device's 12/24-hour setting.
  • Full-width FilterRows that open lazy, searchable single- and multi-select pickers.
  • A full-screen DateRangePickerDialog.
  • SegmentedControl for single choices.
  • LazyGroupedRows for long grouped lists.

The screens moved to this style are:

  • search options and Reports filters;
  • the start-timer sheet with a Shortcuts section, and the sync and long-timer cards;
  • the Calendar entry-actions, settings, overlay and split sheets;
  • Review: Inbox, Inbox settings, Review day, the project picker and reminders;
  • the template editor and templates list;
  • Sync Center, Privacy, login server config, the Quick Settings start screen and the organization switcher.

The old chip clouds, M3 segmented buttons, radio-button dialogs and five different time pickers are gone.

Bug fixes (verified by audit)

  • Data loss:
    • A failed token refresh used to wipe the outbox and every unsynced entry. It now only signs out, and data is wiped only when a different account signs in.
    • Queued edits no longer vanish when START/CREATE/STOP and UPDATE sync back to back.
    • Undo after delete keeps queued edits.
    • Clear cache is blocked while changes are waiting.
    • Log out warns how many changes would be lost; Sync Center discards ask first.
  • Timer:
    • Saving the running-entry details after Pause no longer restarts the timer.
    • Edits made there are no longer reverted on Stop or Resume.
    • Notification, tile and widget start/stop go through Room and the outbox.
    • STOP checks the server's active timer and never overwrites an end set elsewhere.
    • Conflicts never drop a queued STOP.
    • START sends tags and billable.
  • Stuck sync:
    • 401s and short outages no longer dead-letter every queued change.
    • Sync now / Retry replace backed-off work instead of queueing behind it.
    • The first 429 stops the run and schedules the next one from Retry-After.
  • Other:
    • Rotation no longer replays the launch intent (org switch, deep link, OAuth code).
    • The Running search option works.
    • Go to date works for a day that is already loaded. That jump fetched the same page again, the unchanged list was never rebuilt, and the jump waited for a rebuild forever. It also lands on the right day, and the newer page loads only after the jump has scrolled.
    • Drag and split write canonical UTC timestamps.
    • Reports reflects offline edits.
    • Boot no longer starts a foreground service, which could crash on Android 15.
    • Ghost soft-deletes are committed.
    • A running snapshot is never treated as equal to a stopped one in conflict checks.

Performance

  • The Calendar no longer recomposes the whole grid every second while a timer runs.
  • Reports fetches only the selected and comparison ranges (2-day carry-in) and caches them for 5 minutes. It used to page the entire history on every load.
  • The month carry-in dropped from 31 days to 2.
  • Room: a composite (organizationId, start) index, one joined entries query with distinctUntilChanged, and a conservative prune. This is schema v9 with MIGRATION_8_9.
  • Calendar and Inbox collect their data only while on screen (WhileSubscribed). The catalogue refresh runs concurrently, is reused for 5 minutes, and skips unchanged rows.
  • There is no runBlocking or JSON decoding on the main thread at startup. The per-second tick and start-sheet typing recompose only the parts that change.

Scope and acceptance criteria

  • Every screen, sheet, dialog and picker uses the grouped design language; large catalogues open in lazy searchable pickers.
  • Data-loss, timer, stuck-sync and crash defects from the audit are fixed, with JVM regression tests.
  • Calendar, Reports, startup and list performance issues from the audit are addressed.
  • English, Dutch and Japanese strings are updated for every new string; unused strings are removed.
  • README screenshots are regenerated; the Room v9 schema is exported with a migration test.

Validation

Commands and focused tests actually run:

.\gradlew.bat --no-daemon spotlessCheck testDebugUnitTest lintDebug assembleDebug assembleDebugAndroidTest
  -> 981 unit tests, 0 failures; lint 0 errors (67 warnings); debug and androidTest APKs built
.\gradlew.bat --no-daemon assembleBenchmark -> passed (R8-optimized build)
.\gradlew.bat --no-daemon :app:recordRoborazziDebug --tests "*ReadmeScreenshotsTest*" -> README screenshots regenerated
git diff upstream/master...mobile-redesign -> only this change set (branch based directly on upstream/master)
devenv tasks run android:gate -> not run: devenv is unavailable in this workspace

Verification status by scope:

  • Host/unit/lint: the Windows host gate above passed, including new tests for the drag gesture, the running-timer swipe and controls, the filter pickers and date range, search options, calendar sheets, the Reports bounded fetch/cache/overlay, templates, Inbox/Review, Sync Center and Privacy, plus SyncWorker, repository, TokenAuthenticator, TimerCommands, the Room migration and reminders.
  • Screenshot/UI: JVM Roborazzi renders (README set plus the previews below) were inspected in the dark theme. They are not device captures.
  • Device/E2E: the androidTest APK compiles and the robots were updated for the new layouts, but the instrumentation/E2E suites were not run on a device or emulator for this PR.
  • Backend/server evidence: sync behaviour is covered by JVM tests against a mocked remote. STOP now reads the active-timer endpoint before stopping, and the mock server already supports that. No live Solidtime run.

Evidence and remaining gaps

README screenshots, regenerated in this PR:

Time Tracker Start timer Calendar — Day
Time Tracker Start timer Calendar day
Reports Review day Inbox
Reports Review day Inbox
Menu Templates Settings
Menu Templates Settings

Redesigned sheets and pickers (sample data, in .github/screenshots/previews/):

Search options Searchable project picker Reports filters
Search options Project picker Reports filters
Custom date range Running timer details Calendar entry actions
Date range Running timer details Calendar entry actions
Calendar settings Template editor Sync Center
Calendar settings Template editor Sync Center

Remaining gaps:

  • The device/E2E suites were not run; the physical-device gate is still needed.
  • The first launch runs the Room 8→9 migration. It is covered by a migration test but has not been exercised on a real upgraded install.
  • The baseline profile is still the blanket app-code wildcard. A generated profile (startup, history scroll, calendar) needs a device.
  • Reports can briefly show an entry that was deleted during the undo window, and its fetch does not yet back off on 429 the way sync does.
  • Discarding a never-synced entry's failed START removes the local entry.

Safety and working tree

  • No tokens, work data, or machine-specific paths are included.
  • Unrelated changes were preserved.
  • Generated artifacts are intentional and reviewed: the README screenshots, the preview renders and the Room 9.json schema.

Default light/dark schemes now use the iOS system greys, blue accent and red
destructive colour. Adds Dark, Verdant and Wallpaper theme modes so the previous
looks stay selectable.
…ked trend segments

StatRange presets now carry a StatPeriod and StatOffset so the Dashboard's
two segmented controls map to a range through StatRange.preset. Half-month
ranges (1st-15th, 16th-end) join Day, Week and Month; Last7Days is gone.
Each TrendBucket carries per-project segments ordered by range total.
A floating pill tab bar now hosts Timer, Dashboard and Settings. Calendar and
Review open from the Timer header and keep their routes, so deep links and
review notifications still resolve. Everything from the old Track drawer lives
in a grouped Settings tab, with reminders, templates and the sync center added.
Pushed screens keep their owning tab selected; re-tapping a tab pops to its root.
Equal-width Role.Tab segments in a selectable group on a rounded track,
with a raised thumb that slides to the selection. The visible track is
32dp but each segment keeps a 48dp touch column. Adds the segmented and
dashboard chart tokens to Dimens and the track/thumb colour roles.
SelectorStyle.Grouped draws the project, task and tag selectors as iOS-style
rows while keeping their pickers and test tags; the tagged node owns the value
text so tests can read it in either semantics tree.
Large title with export, period and Current/Previous segmented controls,
a grouped filter row, a total card with billable, average, entry count
and the change against the previous window, stacked per-project bars by
day or week with an hour axis, and a per-project breakdown with share
bars. Tapping a bar or project keeps opening the drill-down sheet.

The donut, the old bar charts, the KPI grid and the comparison card are
removed with the strings and tests only they used; PeriodComparison
keeps just the total delta the new card shows.
The timer bar is a compact row with the running description, project, clock
time, pause and a red stop button; tapping it edits the running entry. Idle, it
shows a borderless 'what are you working on' field with a play button and
grouped project, task, tag and billable rows. History renders as full-width
rows in grey day sections with clock-style totals, tag chips and a billable
marker. Tap edits, swipe left deletes, swipe right resumes (also exposed as
TalkBack actions).
Both the Timer sheet and the Calendar/Inbox editor now share a Cancel/title/Save
header, a borderless description cell, grouped project, task, tag and billable
rows, a Time section with date and time chips plus a duration stepper, and a
destructive Delete entry cell. The Timer sheet gains delete (with the existing
undo snackbar). Unused field-style helpers are removed.
surfaceContainerHighest now equals the grouped-cell colour, so default Material
cards (Inbox, Review, Statistics) read as iOS cells; chips and switch tracks use
surfaceVariant. Calendar, now pushed from the Timer header, shows a back button.
NavController.currentBackStack is library-internal (lint RestrictedApi). The
selected tab now follows destination changes: tab routes select themselves and
pushed destinations keep the tab they were opened from. Also drops strings the
redesign orphaned.
Adds Timer idle and Settings shots, relabels Track/Statistics as Timer and
Dashboard, and renders the hero set in the default dark theme.
Day headers now read "Wed, Jun 10", adding the year only
outside the current year. Today and Yesterday keep their labels.
Replace the floating tab bar with a side menu (Time Tracker, Calendar,
Reports, Review, Settings) whose header shows the account and the
organization switcher. Time Tracker docks the running timer at the
bottom with an orange stop button; idle, a + button unfolds into Timer
(the start-timer sheet with favourites and continue-last) and Manual.

Calendar opens on a day view: date and total, a week strip
with the selected day circled, grey entry cards with the project line in
its colour, and Today, Day/Week/Month, break, overlay and settings in
the overflow menu. The month grid uses explicit colours so dark mode no
longer dims it. Coloured project dots are gone outside the dashboard
legend.
Calendar: swipe left/right pages the day, week or month; entries move
after a hold, so swipes and scrolling work from anywhere; a tap opens
Continue, Edit, Duplicate, Split and Delete. Hour labels sit centred on
their lines beside a gutter divider.

Time Tracker: search moves behind a header search button, with the
options in a sheet. Deletes ask first, and swiping a stack left deletes
the whole stack after an "N entries" prompt. The entry menu offers
Continue. Review checks (no project or description, overlap, long entry)
show on each card, and a failed sync retries from a refresh button.
Review leaves the side menu for Settings, and every page is opaque so
the back swipe no longer shows the page below through it.

Calendar months now load from a month before their start instead of
the whole history, reuse months loaded in the last minute when paging,
and wait out HTTP 429 Retry-After before retrying.
- Moves and splits are written with formatTimeEntryInstant (UTC Z), like
  every other local writer, so Room ORDER BY start and identity lookups
  stay text-sortable offline.
- A drop compares instants, not strings, so a hold released on the
  entry's own slot no longer sends an update.
- The drop is the entry's real start plus the dragged time, so an entry
  that starts before the visible hours (drawn clipped at the grid top)
  no longer jumps by the hidden part.
- The month heat colour no longer divides by zero when no loaded day
  has work time.
The calendar's Room entry and sync-state streams were collected from
setOrganization for the ViewModel's whole life, so after one visit every
database write rebuilt the day buckets even on other tabs. uiState now
joins them on with stateIn(WhileSubscribed(5s)) and the screen collects
with collectAsStateWithLifecycle. Buckets are rebuilt when the account
zone changes, data from a previous organization is never shown under a
new one, and "stale" is derived from cached days being on screen.

Months stay fresh for five minutes instead of one (Retry still forces a
reload, and returning to the calendar refreshes months that went stale),
and adjacent months are prefetched only after the visible page loaded
and settled, so quick paging does not download months it passes.
- Calendar drag read the gesture inside the block's own drag offset, so
  each pointer position was relative to the moving block and the entry
  chased itself. The gesture is now read outside the offset, the block
  settles on the snapped slot until the move lands instead of flashing
  back, a second drag starts from the new position, and a dragged
  entry draws above the neighbouring week columns.
- Entry actions and Sync & recovery offer Retry for waiting and
  retrying changes, not only failed ones.
- The running-timer bar has a handle and opens the running entry's
  details when swiped up; the details keep the live clock with pause,
  resume and stop on top.
With a running entry visible, the week grid and the month's day view
read a per-second clock high in the tree, so every second each column
re-ran layoutTrackedEntries (parsing every entry), recomposed every
block and drag modifier, and rebuilt the header formatters.

The clock is now a State: layout reads a minute-granular derived value
and a day's layout is remembered, relaid out each minute only when it
holds a running entry. Each entry block is a keyed composable of its
own, and only a running block reads the second clock for its height and
duration. The now line moves in an offset {} lambda without
recomposing, the period and date titles are remembered, and the drag
modifier no longer parses the entry start on every pass.
GroupedRow gains a status-icon tint, a TalkBack click label and a
single-line mode for rows that show user data; EntryDatePickerDialog
takes the confirm label, so other date pickers can share its filled
confirm and text Cancel. Both are new parameters with defaults.

Adds the Time Tracker sweep strings (English, Dutch, Japanese).
- Search options follow the entry form: a Date choice (any time,
  today, 7 days, custom), client, project, task and tags as full-width
  rows, then billable and the status checks as switches.
- Statistics filters use the same layout: billable as a segmented
  choice, then projects, clients, tasks and tags as full-width rows.
  Projects were a chip for every project in one scrolling column, which
  lagged on large catalogues; every row now opens a searchable, lazily
  listed picker like the entry form's project picker.
- Custom date ranges open a full-screen range picker instead of the
  range picker squeezed into a date dialog.
Creating a project picked its client from a dropdown menu composing
every client at once. It is now a grouped Client row opening a lazy,
searchable picker: No client first, then Create client (named after
the search) so it is reachable without scrolling, then active clients.
The client-picker and create-client test tags are unchanged.

The overlap warning parsed every existing entry four times on each
time change. Entries are now parsed once per list, narrowed to the
edited days (plus a day either side) only when a date changes, and
checked with the same half-open rule as EntryTrustRules.overlaps.
…king state

Running entry (B1, B2): saving the running entry's details now asks Room
whether it still runs. If Pause, the notification or another surface ended
it while the sheet was open, the edits are saved onto the ended row with its
recorded end instead of clearing the end and restarting the timer; a start
after that end is refused with a message. Saving the running entry, or an
edit to its Room row from anywhere, updates the editing fields that Stop and
Resume commit, so they no longer write the old description, project or tags
back. Stop and Pause take the details sheet's pending fields, including a
changed start, and commit them with the stop.

Deleting the running timer (B9) ends it locally like Stop: timer, docked
bar, notification and widget, and keeps the 10-second poll from showing the
still-active server copy until the deferred DELETE syncs; undo brings it
back.

Editor requests (B6, B10): Duplicate and Split can skip the Time Tracker's
open-the-copy request, and such a request expires after 10 seconds.
continueEntry refuses while a timer runs or is paused, so a paused timer's
fields are never overwritten.

Performance: no runBlocking for the time-zone policy (device zone until the
account policy is read off the main thread); the first-frame cache holds
only the newest entries and the catalogue items they show, and is rewritten
debounced and only when that changed; observeLocalData projects Room
without waiting for a refresh; the paginated history merge runs on a
background thread in one pass and is redone if the window changed
meanwhile; the active-entry poll re-posts the notification only when its
content changed.
AppSheet gives every bottom sheet the search-options layout, ConfirmDialog
one confirm style (red for destructive actions), AppTimePickerDialog one
time picker that follows the device 12/24-hour setting. Full-screen
pickers put close on the left like the date-range picker, grouped selector
icons are tinted like GroupedRow, option pickers tick the choice on the
right, and the unused SearchableSingleSelectDialog is gone.
- Entry actions: AppSheet with a short summary, the unsent change as
  its own section (state, Retry/Discard/Sync centre, explanation as the
  footer), actions as grouped rows (Continue first, Edit, Stop,
  Duplicate, Split) and Delete as a destructive row.
- Settings: AppSheet with Done; grid size and snap interval as
  segmented controls; visible hours as rows opening an hour list.
- Overlay: AppSheet; the switch as a grouped switch row with the
  privacy note as its footer, calendars as toggle rows in one section
  (no nested fixed-height scroll) with the overlay status as footer.
- Delete uses the destructive ConfirmDialog; the split dialog uses the
  device 12/24-hour clock, a date value chip opening the app's date
  picker, and a filled Split button.
- The running-timer card is a grouped cell with a red stop, like the
  Time Tracker timer bar.
- Continue is hidden while a timer is running or paused: a paused timer
  has no running entry, and continuing then only overwrote its details.
  CalendarScreen takes timerActive (default false) for that.
- The sync-status map, the rationale check and the dialogs' entry list
  are no longer rebuilt on every recomposition.

Existing test tags stay on the equivalent clickable nodes; the snap
setting is now selected by its segment tag instead of a value label.
…r re-anchoring

- BootReceiver started the dataSync foreground service from
  BOOT_COMPLETED. With targetSdk 35+ Android forbids that and throws
  from the service's startForeground(), long after the guarded
  startForegroundService() call returned, so the app crashed. Boot now
  always posts the plain resume prompt (and consults Room when the
  network is not up yet); startForeground() failures are caught and the
  notification is posted without the foreground service.
- The tile's error notification reused id 1001, the running-timer
  notification, so an error replaced the timer and its Stop action.
  It has its own id now.
- ReminderScheduler re-anchored with UPDATE and an initial delay, but
  WorkManager applies an initial delay only to the first period: after
  one run, a changed reminder time (and the worker's own DST re-anchor)
  was ignored and the reminder kept firing 24 h after the last run.
  The request now also sets the next-run override. The new test fails
  without it (next run 24 h out).
The drill-down behind a tapped bar or project row used a default partially
expanded sheet, a regular title, bare dividers, an ad-hoc spinner and a list
capped at 460 dp. It now opens fully expanded on the page background with the
bold sheet title, lists the entries as one grouped section drawn per lazy
item (so a busy slice composes only its visible rows and fills the sheet), and
uses the shared loading and empty states.
…rune old copies

The per-organization history is read unbounded and sorted by start on
every Room invalidation, and observeTimeEntries combined three flows
(entries, tags, tag refs), so one write produced two or three
emissions through every screen.

- Schema v9: a composite (organizationId, start) index replaces the
  organization-only index (MIGRATION_8_9, exported schema 9.json).
- observeTimeEntries is one joined query (entries + tag refs + catalogue
  tags, tag order kept) with distinctUntilChanged, so a write emits once
  and writes that change nothing visible are not re-delivered. The
  public flow keeps its shape, ordering and tag semantics.
- A conservative prune, once per process after a successful refresh,
  drops SYNCED copies started over 400 days ago and unwritten for 30
  days. Pending, conflicted, running or outbox-referenced rows and
  anything inside a month loaded in this process are never pruned; a
  month load or pull restores them.
The dashboard was one verticalScroll column whose legend and breakdown held
a row per project and whose estimates card listed every estimated project in
the organization, whatever the range.

The cards are now lazy items under pull to refresh, loading and the failed
refresh use the shared state views, and the legend, bars and breakdown name
the six largest projects with the rest folded into one Other swatch, segment
and row that opens their entries in the drill-down. Estimates list only
projects with time in the filtered range, six most urgent first.
loadMonth started each month query 31 days early so an entry starting
before the month still showed up. Every month load therefore also
downloaded most of the previous month, while CalendarViewModel already
prefetches the adjacent months (whose own loads put any long entry into
Room). Two days of carry-in cover entries that cross midnight into the
month.
The template sheet used outlined fields, a FilterChip cloud over every tag,
a checkbox, bottom-aligned Cancel/Save buttons and a red text-button delete
confirmation. It now mirrors the entry form on the page background: Cancel,
title and Save across the top, borderless name and description cells, the
grouped project/task selector, the searchable tags picker and a billable
switch, a favorite switch with its explanation beneath, and when editing a
Delete row behind the shared destructive confirmation.
refreshAll ran on every foreground return (>30 s away) and fetched all
catalogue pages, then the entries, then the memberships one after the
other, and rewrote every row even when nothing had changed, waking
every Room observer.

- Catalogue, entries and memberships are fetched concurrently.
- The catalogue is reused when it was fetched less than 5 minutes ago
  in this process and Room still holds that pull; refreshAll gained an
  optional forceCatalog flag so an explicit user refresh can bypass it
  (existing callers are unchanged).
- Catalogue and membership lists are only upserted when a row is new or
  differs, and applyServerEntries skips entries identical to Room, so
  an unchanged refresh performs no writes.
Templates were elevated cards with shadows, the screen drew its own loading,
error and empty layouts, and it used an extended FAB. The templates are now
one grouped section drawn per lazy item (tap a row to edit; the star and the
overflow menu stay), the states use the shared views, the add button is the
round primary FAB used on Track and Calendar, and editing offers Delete.
The README screenshot composition follows the grouped layout; its baseline
is not re-recorded here.
observeCachedAuth mapped every change notification to a fresh
getCachedAuth(), so each subscriber (TemporalPolicyProvider for every
screen, Sync center) decoded the user and membership JSON again, and
emitted even when nothing changed.

The decode is memoised per change version and the flow is distinct;
TemporalPolicyProvider.policy is distinct too, so a profile or
membership rewrite that keeps zone and week start no longer makes every
statistics/calendar collector re-resolve and re-fetch. The synchronous
first-frame read is unchanged. cacheCurrentMembership now bumps the
change version because it is part of the cached auth.
ktlint max-line-length (spotlessCheck) flagged test fixtures added in
the preceding commits.
Wires the calendar's timerActive so Continue stays hidden while a timer
is paused.
Reports and Templates both build grouped sections from lazy items; the
helper belongs with GroupedSection.
…rces

Calendar day buckets were built on Dispatchers.Default, so under a loaded
full-suite run a test could assert before the first state arrived. Tests
now build them on their own dispatcher. Snackbar text in Privacy and
Review day reads LocalResources, which follows configuration changes.
…efetches the catalogue

The repository now sends billable on START; the Time Tracker passes the
editing choice, so the Billable switch in the start sheet, Continue and
favourites is no longer dropped. A user refresh bypasses the repository's
five-minute catalogue reuse.
JVM renders (Roborazzi) of the search options, pickers, Reports filters,
date range, calendar sheets, running-timer details, template editor and
Sync Center with sample data, for the pull request description.
@AKolenda AKolenda mentioned this pull request Sep 24, 2026
The benchmark build installs beside the release app under its own
application id; a distinct launcher and chooser name tells them apart
when both are on one phone.
The play button, swipe and Continue action on a history entry, and the
Calendar's Continue, were offered only while no timer ran. They now work
at any time: a running timer is stopped with the fields it shows and the
chosen entry starts, in one Room transaction, so one tap moves tracking
onto that job. A paused timer is ended the same way. Entries that are
still running offer no play button.
Tapping a project in Estimates & progress lists its time entries for the
selected range, the same drill-down the project rows and day bars open.
Tapping a job in Estimates & progress listed only the account's own
entries in the selected range, so it could never add up to the job's
spent total, which counts every date and every member. The list now
fetches every entry on the project from the server, paged to the end:
everyone's time with their names when the role may see it, otherwise
the account's own entries with a one-line note. Room's copies replace
server rows, and offline the phone's entries are listed with Retry.

Every drill-down list is also live now. One opened before the range
finished loading used to keep only the entries cached on the phone; it
now fills in when the server answers and follows local edits.
A jump to a day in the loaded history fetches that same page again. The
list is rebuilt only when its entries change, so the equal list was never
rebuilt, and the jump waited for a rebuild from that exact list object: it
never scrolled and was never consumed. Compare the lists by content.

While a jump is pending, the newer page is not requested from the list's
old position at the top; it loads once the jump has scrolled.
Entry cards are tinted with their project colour and led by a colour bar,
as before the redesign. The tint is laid over the page background, so the
hour lines and an overlapping entry do not show through. The README
calendar screenshots are regenerated.
@Tricked-dev
Tricked-dev merged commit 9e4db28 into Tricked-dev:master Sep 27, 2026
0 of 3 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.

2 participants