diff --git a/apps/web/src/home/ThisWeek.svelte b/apps/web/src/home/ThisWeek.svelte
index ee6bae8..2013b1b 100644
--- a/apps/web/src/home/ThisWeek.svelte
+++ b/apps/web/src/home/ThisWeek.svelte
@@ -1,69 +1,12 @@
-
-
- {hoursLabel(week)}
- Logged on Jira
-
-
-
-
+
-
-
diff --git a/apps/web/src/home/ThisWeek.test.ts b/apps/web/src/home/ThisWeek.test.ts
index 8781663..ba3ffd2 100644
--- a/apps/web/src/home/ThisWeek.test.ts
+++ b/apps/web/src/home/ThisWeek.test.ts
@@ -3,12 +3,10 @@ import { expect, test } from 'vitest';
import ThisWeek from './ThisWeek.svelte';
-test('shows the hours logged against the target, and links to the day report', () => {
- const { container } = render(ThisWeek, { props: { week: { loggedHours: 30, targetHours: 40 } } });
+test("shows the week's card under its head, which links to the day report", () => {
+ render(ThisWeek, { props: { week: { loggedHours: 30, targetHours: 40 } } });
expect(screen.getByRole('heading', { level: 2, name: 'This week' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Day report' })).toHaveAttribute('href', '/time');
expect(screen.getByText('30h / 40h')).toBeInTheDocument();
- expect(screen.getByText('Logged on Jira')).toBeInTheDocument();
- expect(container.querySelector('.fill')).toHaveStyle({ width: '75%' });
});
diff --git a/apps/web/src/home/api.ts b/apps/web/src/home/api.ts
index d90f39c..8364c55 100644
--- a/apps/web/src/home/api.ts
+++ b/apps/web/src/home/api.ts
@@ -1,3 +1,5 @@
+import type { Week } from '../time/week';
+
/** The apps that have items so far, as home names them. */
type App = 'github' | 'planner' | 'pulls' | 'time' | 'todos';
@@ -21,12 +23,6 @@ interface SourceStatus {
state: 'ok' | 'unavailable';
}
-/** The hours logged this week, against the weekly target. */
-export interface Week {
- loggedHours: number;
- targetHours: number;
-}
-
/** The Home screen's data from GET /api/home, with only the fields the app reads so far. */
export interface Home {
name: string;
diff --git a/apps/web/src/time/WeekCard.svelte b/apps/web/src/time/WeekCard.svelte
new file mode 100644
index 0000000..6f0e122
--- /dev/null
+++ b/apps/web/src/time/WeekCard.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+ {hoursLabel(week)}
+ Logged on Jira
+
+
+
+
+
+
diff --git a/apps/web/src/time/WeekCard.test.ts b/apps/web/src/time/WeekCard.test.ts
new file mode 100644
index 0000000..8a6ad1f
--- /dev/null
+++ b/apps/web/src/time/WeekCard.test.ts
@@ -0,0 +1,13 @@
+import { render, screen } from '@testing-library/svelte';
+import { expect, test } from 'vitest';
+
+import WeekCard from './WeekCard.svelte';
+
+test('shows the hours logged against the target, and fills the bar as far', () => {
+ const { container } = render(WeekCard, { props: { week: { loggedHours: 30, targetHours: 40 } } });
+
+ expect(screen.getByText('30h / 40h')).toBeInTheDocument();
+ expect(screen.getByText('Logged on Jira')).toBeInTheDocument();
+ expect(container.querySelector('.track')).toHaveAttribute('aria-hidden', 'true');
+ expect(container.querySelector('.fill')).toHaveStyle({ width: '75%' });
+});
diff --git a/apps/web/src/home/week.test.ts b/apps/web/src/time/week.test.ts
similarity index 100%
rename from apps/web/src/home/week.test.ts
rename to apps/web/src/time/week.test.ts
diff --git a/apps/web/src/home/week.ts b/apps/web/src/time/week.ts
similarity index 87%
rename from apps/web/src/home/week.ts
rename to apps/web/src/time/week.ts
index 32e3420..6062ae8 100644
--- a/apps/web/src/home/week.ts
+++ b/apps/web/src/time/week.ts
@@ -1,4 +1,8 @@
-import type { Week } from './api';
+/** The hours logged this week, against the weekly target. */
+export interface Week {
+ loggedHours: number;
+ targetHours: number;
+}
const minutesPerHour = 60;
const full = 100;
diff --git a/docs/plan.md b/docs/plan.md
index 898f73b..d592b92 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -165,6 +165,7 @@ The v1.2 design in §4 to §6 stays as reference. A piece of it is built only wh
| `time-report` | A package of the new `time` service, `report`, works out the time report from my worklogs: the hours I logged on each of the last 7 days, the weekdays before today with nothing logged marked as missing, and this week's hours against its 40-hour target. The third of `time-tracking`'s twelve steps (decision 181) | Done (#149) |
| `time-service` | A new `time` service polls Jira for my worklogs of the last 7 days as it starts and every 5 minutes after, and answers the time report at `GET /time/report`, with when the latest poll to succeed began and why the latest poll failed, if it did. It reads todos' Jira settings and token: without them it answers 404, and with only some it doesn't start. `mise run up` runs it on port 8088 and the Gitea workflow builds its image; the gateway reaches it with `time-gateway`. The fourth of `time-tracking`'s twelve steps (decision 182) | Done (#150) |
| `time-gateway` | The gateway forwards `/api/time` to the `time` service, behind both of its locks, so the app reaches my time report at `GET /api/time/report`. `time` is one of the gateway's peers, each of which `BAKHYARD_PEERS` must name, and `mise run up` starts the gateway once time is ready. The sixth of `time-tracking`'s twelve steps (decision 181) | Done (#151) |
+| `week-card` | The Home's "This week" card, the hours logged against the target over a bar, moves with its formatting and the week's type to the Time app's own folder, `src/time`, as a component of its own, `WeekCard`, ahead of `time-page`, which shows it too; nothing changes on screen or in behaviour. The seventh of `time-tracking`'s thirteen steps (decision 183) | Done (#152) |
| `nudge-merge` | Nudge, the user's phone-questions tool for agents (`codedogapp/nudge`), moves into Bakhyard: one app, not two (decision 157). The nudge session does the work, one small PR per step, from its plan, [`bakhyard-merge.md`](https://github.com/codedogapp/nudge/blob/main/docs/bakhyard-merge.md); this session doesn't stop its own steps for it | In review |
After these: the product phases (§8.2), each split into steps.
@@ -1195,13 +1196,14 @@ Every item is pending unless it says done. The service an item lands in is in br
- `pulls-reminders`: at 10:00 and 15:00 on weekdays, pulls pushes one reminder that lists my PRs under their required approvals (decision 177). Done (#144).
- `homelab-pulls-notify`: done (codedogapp/homelab#40). In the homelab repo, pulls' `BAKHYARD_PEERS` names notify, `TZ` gives it the user's zone, and a NetworkPolicy lets pulls reach notify.
- Later, with a database for pulls: snooze (2 h / tomorrow / until updated) and mark as nudged, from the PRs page.
-- `time-tracking` [time]. Needs `todos-jira`. Reads and writes standard Jira worklogs (§2, decision 46), with no timers: Clockwork already has them inside Jira. Twelve steps (decisions 180 and 181):
+- `time-tracking` [time]. Needs `todos-jira`. Reads and writes standard Jira worklogs (§2, decision 46), with no timers: Clockwork already has them inside Jira. Thirteen steps (decisions 180, 181 and 183):
- `shared-jira`: todos' Jira client moves to the root `internal/jira`, with the code that reads its settings and token, for the `time` service; todos' poller of my tickets moves to a package of its own, `tickets`. Nothing changes in behaviour. Done (#147).
- `jira-worklogs`: the client reads my worklogs in a span of days: my account from `/myself`, the tickets I logged on from a `worklogAuthor` search, and each ticket's worklogs. Done (#148).
- `time-report`: a package of the `time` service, `report`, works out the time report from my worklogs: the hours I logged on each of the last 7 days, with every weekday before today that has none marked as missing, plus this week's total against the target. Done (#149).
- `time-service`: a new `time` service polls the last 7 days' worklogs every 5 minutes and answers the time report at `GET /time/report` (decision 182). Done (#150).
- `homelab-time`: done (codedogapp/homelab#42). In the homelab repo, `time` runs on the laptop with todos' Jira settings and token, and the gateway's and home's peers name it.
- `time-gateway`: the gateway forwards `/api/time` to `time`. Done (#151).
+ - `week-card`: the Home's week card, its formatting and the week's type move to `src/time`, for the Time page to share; nothing changes on screen (decision 183). Done (#152).
- `time-page`: the Time page shows the time report.
- `time-week`: `time` answers `TimeService/GetWeek` (§4.10).
- `home-week`: home asks `time` for the week, and the Home's "This week" shows it, or "Hours unavailable" when `time` doesn't answer (§7).
@@ -1554,6 +1556,7 @@ Made on 2026-10-23:
- **The zone**: days follow `time.Local`, the Mac's zone or the container's `TZ` (decision 180). The distroless static image holds tzdata (its README lists it), so `TZ` works without importing `time/tzdata`, which would add about 450 KB to the binary. Without `TZ` a container keeps UTC, so `homelab-time` sets it, as `homelab-pulls-notify` did for pulls.
- **The image**: `services/time/Dockerfile` builds as pulls' does, and the Gitea workflow builds and pushes `time` with the other services.
- **Rejected**: the poller inside `report`, which would bring the clock and Jira into it; a Refresh that polls on demand; embedding `time/tzdata`; `time.Now()` read twice, once for the span and once for the report, which could disagree across midnight.
+183. **The Time page in two steps**: the lead's choices (2026-10-29), for the user to review. The Time page shows this week's hours against the target on the card that the Home's "This week" shows, so the two share it. The card sat inside the Home's `ThisWeek`, under the section head whose "Day report" link leads to the Time page, with its formatting and the `Week` type in the Home's folder. So `time-page` splits in two and `time-tracking` has thirteen steps (§8.2): `week-card`, a pure refactor, moves the card into a component of its own, `WeekCard`, and moves it, `week.ts` and the `Week` type to the Time app's folder, `src/time`, as `shell-section` (#85) moved the section head ahead of `todos-groups`; `time-page` then builds the page. The Home takes the week's card from the Time app, as home will take the week from the time service (`home-week`). Rejected: the Time page importing the Home's files, which would make an app depend on the Home, as `MorePage` does on its app cards; a copy of the card.
## 10. Risks