Add host-heph-pwa.md: a deployment how-to for serving the PWA from the canonical
hub in the hub/spoke OIDC setup (post-release) — fetch the shell at the hub's
tag, add --web-root, terminate TLS (tailscale serve / reverse proxy), and the
token-paste caveat with the device-code-login follow-up. Cross-linked from
heph-pwa and the how-to index.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The PWA shares the daemon's store with the TUI/desktop popover but only
re-fetched on a view switch or action — so a task marked done elsewhere left a
stale list on screen. Reload the current view on visibilitychange→visible
(switch back to the phone, unlock, tab re-show), skipping it mid-modal/search.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Popover supervision was gated to Mode::Local, so running the store-owning
daemon in server mode (now needed to host heph-pwa) silently dropped the
desktop quick-capture popover. Server mode is local + an HTTP hub and owns the
same store/socket, so it should drive the popover too; broaden the guard to
Local | Server (client, a thin proxy, still opts out).
Also: when the PWA shell is served from the hub, default the hub URL to its own
origin so the app is zero-config on first open (Settings still overrides). Bump
the service-worker cache to v2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document serving the app from the hub (--web-root), connecting (hub URL +
optional token), quick-add syntax, voice, triage, and the deliberate
design choices (PWA over native iOS; online-only; token paste vs device flow)
with their known limitations to revisit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A buildless, installable PWA that mirrors heph-tui: sidebar of built-in views
(tom/tasks/work/chores/ondeck/inbox) + projects, a task list with attention
flags / project bullets / date chips, tap-to-expand triage (done/drop/skip/
attention/reschedule/move/delete + undo), full-text search, and a read-only
context+log preview. The primary surface is the quick-add modal (FAB or Cmd-'),
which live-parses the TUI syntax into preview chips and supports voice via
on-device dictation / the Web Speech API. rpc.js is the online-only JSON-RPC
client mirroring heph-tui's Backend; settings persist in localStorage. Service
worker caches the app shell for offline launch.
Verified end-to-end against a local server-mode hephd (--web-root): the app
boots, calls the view RPC, and renders RankedTasks in headless Chrome.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Faithful JS ports of hephd's quickadd.rs / datespec.rs so the PWA's quick-add
accepts the identical syntax (p1-4, #Project greedy match, today/+3d/fri/ISO,
'every …' recurrence) and produces the same RRULEs and local-midnight do-dates
as the CLI/TUI. test/parsers.test.mjs replays the Rust unit cases under
`node --test` (13/13 pass).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a permissive CORS middleware (answers the browser OPTIONS preflight and
stamps Access-Control-* on every response) and an optional --web-root static
file handler with an index.html SPA fallback. Together these let a browser
surface — the forthcoming heph-pwa mobile app — call /rpc cross-origin or be
hosted same-origin straight from the hub. No new crate dependencies; file
reads run on the blocking pool. Covered by tests/web_serve.rs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hephd's reqwest client is built default-features=false with no TLS
feature, so the self-update release poll's HTTPS GET always failed
('release check failed: requesting forge releases/latest') — the bug
never surfaced before because nothing in production used reqwest over
HTTPS (hub sync is plain http://). Switch the poll to ureq, which is
already a dependency and ships a rustls/ring TLS stack needing no system
libs (notably no cmake/aws-lc-sys, which would break the rust:bookworm CI
image). Verified end-to-end: a 0.0.0 build now detects v1.1.0, installs,
and restarts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collapse the eight Mikado scaffolding cards (+ goal card) into one
user-facing how-to, docs/how-to/self-update.md: what self-update is and
how to enable it. The per-card breakdown was build-time scaffolding, not
documentation. Keeps the changelog fragment; updates the how-to index.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Public repo => anonymous HTTPS clone, no credentials (the SSH/canonical
premise was wrong: that was the access-restricted cargo registry, not git
clone). Install URL points at the canonical public host (verified end to
end); the service template bakes cargo onto PATH. Card rewritten to
reflect what actually happened.
The repo is public, so self-update needs no credentials: cargo install
--git is a plain anonymous clone (NOT the access-restricted Forgejo cargo
registry, which is what required forge.ops.eblu.me). Point INSTALL_GIT_URL
and the releases poll at the canonical public host over HTTPS — verified
end-to-end (cargo install --git https://forge.eblu.me/... --tag v1.0.3
builds a working hephd with zero auth).
Make the headless service able to run the apply path: 'heph daemon
start --self-update' (default off) generates a launchd/systemd service
that passes --self-update and bakes a PATH (incl ~/.cargo/bin) + HOME so
the minimal service env can find cargo. restart preserves the setting.
Default (no flag) services are byte-identical to before. Template + URL
behavior covered by unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The poller now installs + restarts (not just logs); fix the stale doc and
point at service-env-forge-access as the deployment step that makes the
apply path operational.
Restarter + ProcessRestarter wired: install then exit(0) so the service
manager respawns the new binary; restart only on a successful install.
Unit-tested via injection.
Add a Restarter trait + ProcessRestarter (exit 0 so launchd KeepAlive /
systemd Restart=always respawn the new binary). apply_update now installs
then restarts, and the restart fires only on a successful install. Wired
into the poll loop. Unit-tested with fake installer+restarter: restart on
success, no restart after a failed install. Real process exit is never
run in tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Installer trait + CargoInstaller + apply_update landed and unit-tested
via injection. Real cargo execution is gated on the deployment env
(service-env-forge-access).
Add an Installer trait + CargoInstaller (runs cargo install --locked
--git <ssh> --tag <tag> for heph/hephd/heph-tui/heph-quickadd — the
documented install command, via the SSH host that sidesteps the
cargo/forge canonical-name mismatch), and apply_update() which runs the
blocking install on the blocking pool. The poll loop now applies on a
detected update. Apply path is unit-tested with a fake installer (call +
failure paths); the real cargo subprocess is never run in tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lock in the base-case guarantee that a self-updating hub (which restarts
under its spokes) relies on. New sync_http test: a spoke whose hub is
unreachable keeps serving + accepting writes, a sync attempt fails fast
(Err, not hang/panic), and when the hub returns the accumulated ops
reconcile with no special recovery.
The verification surfaced one non-graceful path — the daemon's shared
reqwest client had no timeout, so a black-hole hub (connects, never
replies) could stall the sync/self-update loop. Give it a 30s timeout so
'the hub can vanish at any moment' holds even mid-request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-restart works by exiting cleanly and letting the service manager
respawn the new binary. launchd already does this (KeepAlive=true), but
the systemd user unit was Restart=on-failure, which ignores a clean
exit (code 0). Switch to Restart=always + RestartSec=1, update the unit
test, and note in run-the-daemon that existing Linux installs must
`heph daemon restart` once to regenerate the unit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a ReleaseSource trait (real ForgeReleaseSource over HTTP; injectable
for tests), check_release() returning a CheckOutcome
(UpToDate/UpdateAvailable/Failed) that never errors so a flaky forge
can't stall the daemon, and run_poll_loop() that ticks on the configured
interval and logs when a newer release is available. spawn_self_update_loop
now spawns the real poller. Detection is unit-tested with a stubbed source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add --self-update (default off) and --self-update-interval-secs to the
hephd CLI, a SelfUpdateConfig (Some => enabled), and thread it into the
Daemon (with_self_update) for every mode. spawn_self_update_loop()
currently just announces the mode at startup ('self-update enabled')
so the opt-in is observable; the poll/apply cycle is wired in later
leaves. Omitting the flag leaves behaviour unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add crates/hephd/src/selfupdate.rs: a pure update_available() that
compares the running heph_core::VERSION (e.g. "1.0.3 (sha)") against a
release tag ("v1.0.4") via semver, ignoring the build suffix and v
prefix; plus parse_latest_tag() / fetch_latest_tag() for the forge
releases/latest feed. Decision logic and JSON parsing are unit-tested
against sample payloads; the network fetch is isolated. Adds the semver
workspace dep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Kick off the C2 Mikado chain for an opt-in (default-off) hephd
self-update mode (forge-poll -> cargo install from tag -> self-restart).
Goal card plus eight prerequisite cards, indexed from how-to.md:
release-poll-version-check, self-update-opt-in-flag (leaves)
-> self-update-poll-loop (notify-only core)
service-env-forge-access (leaf, the cargo/forge blocker)
+ self-update-poll-loop -> cargo-install-from-tag
service-respawn-on-clean-exit (leaf, systemd Restart=always)
+ cargo-install-from-tag -> self-restart-after-update
verify-hub-dropout-resilience (leaf, lock in the base-case guarantee)
Grounded in research of hephd's sync loop, daemon lifecycle, the
launchd/systemd service templates, and the forge releases API.
Captured from Hephaestus task 01KTA2NSNRYT902HC3VRW00S1J.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cargo-fmt failure on this PR slipped to CI because the pre-commit
prek hooks were never installed in the working clone. The existing
cargo-fmt hook reformats in place but only when it runs. Add a
pre-push cargo-fmt-check hook (`cargo fmt --all --check`) that mirrors
CI's Dagger `check` step exactly, so an unformatted commit is blocked
locally before it can reach the runner — even if the pre-commit hook was
skipped or not installed. Filtered to .rs pushes so Rust-free pushes pay
nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The project.resolve socket test had a node.create call over rustfmt's
line limit; CI's `cargo fmt --all --check` flagged it. Wrap it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RPC clients (the hephaestus.nvim plugin, for its `:Heph version`
command) had no way to learn which hephd they are talking to — `health`
returns counts, not a version. Add a tiny `version` method returning
`{ version: heph_core::VERSION }`, the same `X.Y.Z (sha)` string the
binaries print for --version. No store access needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Editing a task's canonical-context doc body previously meant looking up
its `canonical_context_id` (e.g. via `heph list --json`) and then
`heph node update <doc-id> --body`. Add a `heph context <task-id>`
command that resolves the canonical-context doc from the task's outgoing
links and:
* prints the body with no flag,
* `--body <text>` replaces it (`-` reads stdin, matching `node update`),
* `--append <text>` adds a blank-line-separated paragraph.
Errors clearly when the id has no canonical-context doc (e.g. a plain
doc node rather than a task). Purely a client-side CLI convenience — no
new RPC.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `--project <name>` argument matched titles case-sensitively and
exactly, so `--project hephaestus` or `--project heph` failed against a
`Hephaestus` project. Make project-name resolution forgiving but
deterministic, via a tiered match in `resolve_project_id`:
1. exact (case-sensitive) — the historical behavior; always wins
2. case-insensitive exact — only when unambiguous
3. case-insensitive prefix — only when unambiguous
Ambiguous fuzzy matches resolve to None (callers report "no project
named X") rather than silently picking one. This single resolver already
backed `heph list --project` (via project_scope); route the CLI's
task/edit/promote/parent path through it too with a new `project.resolve`
RPC + `Store::resolve_project`, so every `--project` surface behaves the
same.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-tui was the one daily-driver binary that did not answer --version.
Add the same clap `version = heph_core::VERSION` attribute that heph and
hephd already carry, so all three report `X.Y.Z (sha)` consistently.
Addresses the heph-tui half of the cross-binary --version task.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The build-time dirty check used `git status --porcelain`, which counts
untracked files — including cargo's own `.cargo-ok` marker in a
`cargo install --git` checkout — so a clean tagged build reported e.g.
`1.0.1 (bcab3c16b-dirty)`. Use `git diff --quiet HEAD` (tracked changes only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the pre-release 'install from feature/v1-prototype' instructions with
`--tag v1.0.0` as the default, and document `--branch main` as the track-
unreleased-work alternative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heph-core gains a build.rs that captures the short git SHA and a
`heph_core::VERSION` const ("<crate-version> (<sha>)"); heph and hephd use it
for clap's --version. The crate version stays sourced from Cargo.toml.
release.yaml now bumps the workspace version into Cargo.toml + Cargo.lock on a
commit that only the tag points at, tags it manually, and pushes just the tag —
so cargo install --git --tag vX.Y.Z reports the real version while main stays at
0.0.0. The changelog commit moved ahead of the tag so the release includes its
own changelog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents the desired end state before the code change (C1 docs-first):
- release.yaml bumps the workspace version into a commit only the tag points
at, keeping main at 0.0.0
- heph/hephd --version will report the release version plus the build SHA
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Dagger build_docs pipeline cloned Quartz from the default branch
unpinned. Quartz v5.0.0 restructured its config layout (.quartz/plugins,
../quartz imports), breaking the docs build against our existing
quartz.config.ts / quartz.layout.ts. Pin the clone to the last v4
release (v4.5.2) to restore known-good behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>