Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions docs/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ The v1.2 design in §4 to §6 stays as reference. A piece of it is built only wh
| `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) |
| `time-page` | The Time page, at `/time`, shows the time report: this week's hours against the target on the Home's week card, then the last 7 days, a row each with the date, the weekday or "Today" and the hours logged, a Missing chip on each weekday before today without hours, and the missing days' count as the section's note. It loads the report each time it shows, says why the last search failed and when its hours are from, and says when Jira isn't set up or the report couldn't load. The eighth of `time-tracking`'s thirteen steps (decision 184) | Done (#153) |
| `time-week` | The second contract between services, `bakhyard.time.v1`, whose `TimeService.GetWeek` answers this week's hours against the week's target, for the Home's "This week". The time service answers it from the latest read of my worklogs to succeed: with no week when Jira isn't set up, and with `UNAVAILABLE` while no read has succeeded. The ninth of `time-tracking`'s thirteen steps (decision 185) | Done (#154) |
| `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.
Expand Down Expand Up @@ -410,15 +411,15 @@ flowchart LR

### 4.5 Contracts

> **v1.3:** for service-to-service calls and NATS payloads only (decision 43). The public APIs stay hand-written JSON. `attention-contract` built the first contract, `bakhyard.attention.v1`, as decision 158 describes, and pulls and todos answer it (decisions 158 to 160); the rest of this section is the v1.2 design.
> **v1.3:** for service-to-service calls and NATS payloads only (decision 43). The public APIs stay hand-written JSON. `attention-contract` built the first contract, `bakhyard.attention.v1`, as decision 158 describes, and pulls and todos answer it (decisions 158 to 160); `time-week` built the second, `bakhyard.time.v1`, which the time service answers (decision 185); the rest of this section is the v1.2 design.

Schema-first protobuf in `proto/`, a buf v2 module (lint STANDARD, breaking FILE).

| Package | RPCs | Exposure |
| --- | --- | --- |
| `bakhyard.attention.v1` | `AttentionService/ListItems` (NO_SIDE_EFFECTS) → `Item{id, title, detail, source, due}`, with no `url`, since each row links to its app (decision 158). Event message `ItemsChanged{app, reason}`, with NATS | Internal: every feature service implements it; never routed by the gateway |
| `bakhyard.home.v1` | `HomeService/GetHome` (NO_SIDE_EFFECTS, so the client uses HTTP GET) and `WatchHome` (server stream) → `Home{name, items, week, sources, generated_at}` | Public, via the gateway |
| `bakhyard.time.v1` | `TimeService/GetWeek` → `{logged_hours, target_hours}` | Internal |
| `bakhyard.time.v1` | `TimeService/GetWeek` (NO_SIDE_EFFECTS) → `Week{logged_hours, target_hours}` in hours, unset without Jira; `UNAVAILABLE` until a read of the worklogs succeeds (decision 185) | Internal: the time service implements it and home calls it; never routed by the gateway |

`home.v1` messages:

Expand Down Expand Up @@ -1207,8 +1208,8 @@ Every item is pending unless it says done. The service an item lands in is in br
- `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: this week's card, then the last 7 days with the missing ones marked (decision 184). Done (#153).
- `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).
- `time-week`: `time` answers `TimeService/GetWeek` (§4.5) with this week's hours against the target, from the latest read of the worklogs to succeed: no week when Jira isn't set up, and `UNAVAILABLE` until a read succeeds (decision 185). Done (#154).
- `home-time-week`: home asks `time` for the week, and the Home's "This week" shows it, or "Hours unavailable" when `time` doesn't answer (§7).
- `time-attention`: `time` answers the attention contract with today's hours until they're logged, and each missing weekday.
- `worklog-add`: the client adds a worklog, and `time` takes one at `POST /time/worklogs`.
- `fill-day`: the Time page fills a missing day: pick one of my open tickets (assigned to me, not done) and the hours; Bakhyard shows the worklog it will add, starting at 09:00 local time, and writes it only after I confirm.
Expand Down Expand Up @@ -1558,7 +1559,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.
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-time-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.
184. **The Time page** (`time-page`): the lead's choices (2026-10-29), for the user to review. The Time page, at `/time`, shows the time report that the gateway forwards from the time service, `GET /api/time/report` (decisions 181 and 182).
- **The head**: "Time", with no eyebrow and no summary, as the PRs page's head: the sections' heads and notes say what the page holds. Nothing shows under it while the report loads.
- **This week**: a section, "This *week*", with the Home's week card (decision 183) and no link, since the Home's "Day report" link leads here.
Expand All @@ -1567,6 +1568,14 @@ Made on 2026-10-23:
- **Loading and failures**: the report loads each time the page shows, as the PRs page's lists do (decision 149), through the shell's loader of a poller's snapshot (decision 147). A 404 says "Jira isn't set up.", and any other failure "Couldn't load the time report.", under the page head with no section. When the last search failed, one line under the head says why and, when there are days from an earlier search, adds "These hours are from 21 October at 09:38."; one line for the page rather than one in each section, since one snapshot feeds both. Over the sections it leaves the 30 px (24 on phones) that the Todos page's form leaves over its first section (decision 112); with no days yet, the line ends the page, as "Jira isn't set up." does.
- **Code**: `src/time/report.ts` (the `Report` type and `loadReport`), `days.ts` (`dayRows` and `missingNote`), `DayList.svelte` and `TimePage.svelte`; `week.ts` now exports `formatHours` for the rows. The shell's pages gain a `time` kind, so `/time` and the paths under it show the page, as with `/todos` and `/pulls`. The app's tests of the pages it opens on move to a file of their own, `App.pages.test.ts`, since `App.test.ts` had 199 of Biome's 200 lines. `DayList` copies the card's style, as twelve other components do (§0's ideas).
- **Rejected**: a failure line in each section; the newest day first, which runs the week backwards; the weekday as the row's label, with "Today" as a detail; Temporal's `PlainDate` for the dates, where one `Intl` option does; a summary under the head, which would repeat the sections' notes.
185. **The week over the time contract** (`time-week`): the lead's choices (2026-10-29), for the user to review. The second contract between services, `bakhyard.time.v1` in `proto/`, has one RPC, `TimeService.GetWeek`, with no side effects, which home will call for the Home's "This week" (§4.5).
- **The messages**: the answer holds a `Week` message, the shape of `home.v1`'s, so "no week" is one unset field; its hours are doubles, `logged_hours` and `target_hours`, as in the JSON report (decision 181).
- **The answer**: the time service answers from the poller behind `GET /time/report`, so the two always agree: the week of the latest read of my worklogs to succeed, kept through the reads that fail, as pulls' items are (decision 158). A call waits for the first read, and a call whose context ends first gets its context's error.
- **No week or `UNAVAILABLE`**: without Jira, the answer has no week, so the Home will leave "This week" out (decision 161). With Jira set up but no read succeeded yet, the call fails with `UNAVAILABLE`, so the Home will say "Hours unavailable" (§7) rather than look as if Jira weren't set up.
- **A known limit**: a kept week stays until a read succeeds, even past Monday; the Time page says how old its hours are, and the Home won't.
- **Code**: the server is `Server` in `services/time/internal/week`, a package of its own beside `api`, as pulls' `attention` is; `routes` mounts it on the router of the JSON API, with the same poller.
- **The next step**: §8.2's `home-week` becomes `home-time-week`, since `home-week` is the id of a step done in Phase 1 (#24).
- **Rejected**: no week until a read succeeds, which would make a failing Jira look unset; a zero week, which reads as nothing logged; `google.protobuf.Duration` fields, whose seconds the Home would turn back into hours; the read's time in the answer, which the Home doesn't show; the hours as two fields of the answer itself, so that "no week" needs both unset; the server inside `api`, which serves JSON.

## 10. Risks

Expand Down
Loading
Loading