# Shiplog — Architecture Follows the first-party rich-widget pattern (`omarchy.weather`, `omarchy.clock`): one `bar-widget` plugin whose entry point loads an internal popout panel. The `panel` plugin kind is for standalone floating windows (OSD); anchored popouts are part of the bar-widget contract, so Shiplog declares `kinds: ["bar-widget"]` only. ## Modules | File | Role | |---|---| | `BarWidget.qml` | Thin chip wrapper. Loads `Panel.qml`, injects `bar`/`settings`/`anchorItem`/`hostWidget`, forwards the shell shape contract (`open`, `close`, `opened`, `popoutSwitchClosing`, `closeForPopoutSwitch`). | | `Panel.qml` | `qs.Ui Panel` subclass. Owns the data engine (Process + Timer polling), all mutable state, and the popout UI (`KeyboardPanel` + `PanelKeyCatcher`). | | `Model.js` | Pure functions. No QML/Quickshell imports. All parsing, day-boundary math, dedup, grouping, week-strip and cache logic. Unit-tested. | | `scripts/local-commits.sh` | Enumerates repos one level under each configured dir; emits TSV of today's commits. Arguments only; no shell interpolation of config values. | ## Data shapes ``` ShipItem { type: "commit"|"pr"|"issue", repo: string, title: string, url: string|null, ts: number (ms epoch), sha: string|null } Counts { commits: number, prs: number, issues: number, total: number } StateFile { version: 1, days: { "YYYY-MM-DD": Counts } } // week strip cache ``` - `repo` is `owner/name` for GitHub items, absolute path for local-only commits. - `url` is null for local-only commits (row shows repo path, no link). - StateFile lives at `~/.local/state/omarchy/shiplog.json`. Only `Panel.qml` writes it. Pruned to 14 days. ## Model.js API (frozen contract) ``` dayStartMs(nowMs, boundary) -> ms epoch of current logical day start ("HH:MM" boundary) dayKey(tsMs, boundary) -> "YYYY-MM-DD" logical day for a timestamp normalizeSearchPrs(jsonText) -> ShipItem[] // REST /search/issues is:pr response body normalizeSearchIssues(jsonText) -> ShipItem[] // REST /search/issues is:issue response body normalizeSearchCommits(jsonText) -> ShipItem[] // REST /search/commits response body normalizeLocalCommits(tsvText) -> ShipItem[] // local-commits.sh output mergeItems(listOfLists, dayStartMs2) -> ShipItem[] // dedup (sha for commits, url for pr/issue), filter >= dayStart, newest-first groupByRepo(items) -> [{ repo, items }] // group order by newest item counts(items) -> Counts updateStateCache(stateText, key, cnts) -> StateFile object // tolerant of missing/corrupt input weekStrip(stateObj, todayKey, boundary)-> [{ key, label, count, isToday }] x 7, oldest first markdownRecap(items, dateLabel) -> string // "copy day as Markdown" recapFilePath(dir, key) -> string // "