Skip to content

Release v0.3.0 - #203

Merged
rorosaga merged 112 commits into
mainfrom
dev
Oct 6, 2026
Merged

rorosaga merged 112 commits into
mainfrom
dev

Conversation

@rorosaga

@rorosaga rorosaga commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Merging this PR releases Cicada 0.3.0 (G182, TODO ruling 19): CI on main tags v0.3.0 at the merge commit, builds, verifies and publishes the GitHub Release.

Merge it with Create a merge commit — a squash or rebase would give main commits dev does not have.

rorosaga and others added 30 commits October 5, 2026 15:12
Copies the audit (A01–A12, K01), its validation record, handoff prompt and
synthetic repros, and re-applies the TODO.md / memory-evolution.md pointers
on top of current dev (post-G176). Revalidation follows in this branch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Everything still reproduces except A12, which G176 already fixed (the menu
timer is gated on visibility, sleeping displays and Reduce Motion; the
in-app worm draws a static frame under Reduce Motion).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…fe retirement (audit A02/K01/A01)

A02: markdown_parser.write stages the document in a hidden sibling temp
file, fsyncs it and os.replace()s it; an interrupted write no longer
truncates the page. Mode bits and symlinks are kept.

K01 (G114/G135): episode_ids.create_episode creates through a hard link
that never replaces a file and re-mints when another process took the id.
Every capture writer uses it.

A01: Sleep records the body revision it extracted and retires an episode
only when the file still holds it, re-reading under episode_lock (an
flock on the episodes directory) that capture, the stager, MCP and the
agent mark-processed path share for each read-modify-write.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…wording, a real rename-failure test

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…aphs, end interrupted drags (audit A07/A08/A09)

A07: GraphView(isActive:) (Graph tab + window occlusion) pushes
setGraphActive; inactive, graph.js cancels frames and the pulse timer and
stops the d3 timer, resuming without a reheat. A settled graph pulses only
for an on-screen pending node, at ~30 fps on elapsed-time phase.
A08: the coordinator holds the web view weakly; dismantleNSView tears it
down. A09: one cancelInteraction for blur, a buttonless move, pointer
cancel and going inactive. d3-force stays (G109).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…it A04)

copytree/copy2 dereferenced links, so a link inside a bank pulled files
from outside it into the copy. Duplicate now skips links at any depth,
the policy export already had.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…ady, keep focus pins on a cancelled drag

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…eView (G176)

On macOS 26 a TimelineView whose entries fall under ~0.3 s apart makes SwiftUI lay the whole
window out at the display rate: measured 240 host layouts a second for 4 ticks a second, and
the room's sheets run at 80–150 ms. That held the Sleep page at ~12% of one core while the
room changed a few times a second; the sprite code itself never showed in the profile.

SpriteLayerPlayer arms one timer per SpriteFrameSchedule boundary and swaps its layer's
contents to the frame the caller picks, so SwiftUI never re-renders for a frame. SpriteLayerView
and BookwormView use it for moving art; still frames, Reduce Motion, a paused host, an unseen
window and ImageRenderer snapshots keep the plain SwiftUI image, whose mount tears the player
and its timer down. Same frames, same boundaries, nearest-neighbour, no crossfade.

Tests: one draw per boundary with the timeline's frame, no timer outside a window, inert hit
testing, and a lint that bans TimelineView(SpriteFrameSchedule. Full Swift suite 2,792 / 0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015dJDN58iHRxbnW28CHQpr2
12.0% -> 2.54% of a core with the room visible (release builds, 12 x 5 s), SwiftUI renders
120 -> 2.6 a second, 0.83% hidden (the menu-bar worm's own timer). Ruling 18 gains the player
amendment with the TimelineView measurement; the G176 row and the follow-ups handoff mark the
item done; TODO names what remains on that page (the hidden graph's web view, audit A07, and
the stage strip's 0.1 s pulse timeline during a run).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015dJDN58iHRxbnW28CHQpr2
…eep a failed add's draft (audit A03/A05/A06)

A03: Store.entity compares the bank and an invalidation generation across
its fetch; a cross-bank answer is dropped, a pre-invalidation one refetched.
A05: a card-local SourceWriteQueue runs source writes in order, so a
rollback restores only its own change. A06: an add is the EntitySourceAdd
mutation through Store.perform; the draft stays until the answer and a
failure toasts the server's sentence.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…redraws once a minute

With the room's sprites on layers, the clock's 1 s TimelineView was the last thing re-rendering
the Sleep page's whole SwiftUI window every second (~6 ms a render in the profile). Hours,
minutes and the spoken label now move at minute boundaries (TimelineView(.everyMinute)); the
second hand is a SpriteLayerPlayer whose track wakes on every whole second of the sprite clock.
Reduce Motion still shows no second hand; snapshots still draw every hand as an image.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015dJDN58iHRxbnW28CHQpr2
…'s card; pendingAdd cleared only by its own add

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…e loop; walkthrough clock follows visibility and power (audit A10/A11)

A10: sync_ticker computes version + Sleep debt at most once per 0.9 s per
bank; concurrent streams await it; the filesystem half runs in worker
threads with one scandir per directory per tick (bank_index.shared_scans);
sleep_debt.compute scans in a thread; _dir_mtime uses scandir. Every tick
is still a full scan. A11: ExportWalkthrough.cadence pauses the sheet's
timeline while hidden, steps it under Reduce Motion, halves it under Low
Power.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…he graph resting

The wall-clock paragraph, ruling 18's wall-clock and player amendments, and the G176 row now say
the clock redraws once a minute and its second hand is a layer, with the measurement: 1.03% of a
core visible over 90 s (release build with #169), 0 SwiftUI renders in 10 s, the web processes
at 0%. What is left is mostly the menu-bar worm's status-item redraws.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015dJDN58iHRxbnW28CHQpr2
…leep_parked lock, glob-exact dir stamps

Co-Authored-By: Claude Opus 5.5 <[email protected]>
… (iMessage first)

The owner asked whether iMessage, WhatsApp and Telegram conversations can consolidate on their
own once he picks which ones, starting with an AI agent he texts on iMessage. The row records the
three sources (iMessage's chat.db behind Full Disk Access; WhatsApp's undocumented local store,
unverified; Telegram by its own export, since its API would mean signing in as him), the shape on
existing rails (app reads, stager keyed per sitting, nothing read until a conversation is ticked,
a texted agent's replies are its own turns), what is left to decide, and the order.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015dJDN58iHRxbnW28CHQpr2
docs(audit): 2026-10-02 code and performance audit, revalidated
fix(storage): atomic page writes, no-clobber episode ids, revision-safe Sleep retirement (audit A02/K01/A01)
fix(graph): rest when hidden, on-screen-only pulse, release closed graphs, end interrupted drags (audit A07/A08/A09)
perf(sleep): play room sprites and the worm on a layer, not a TimelineView — Sleep page 12% → 2.5% CPU (G176)
…te-symlinks

# Conflicts:
#	docs/architecture/storage.md
fix(banks): duplicating a bank never follows or copies a symlink (audit A04)
fix(app): drop late entity reads, serialise a page's source writes, keep a failed add's draft (audit A03/A05/A06)
# Conflicts:
#	docs/goals/memory-evolution.md
perf(sync): one idle tick per bank shared by every SSE stream, off the loop; walkthrough clock follows visibility and power (audit A10/A11)
docs(backlog): G179 — sync the message conversations the person picks (iMessage first)
rorosaga and others added 29 commits October 6, 2026 13:56
docs(backlog): G183–G185 — every gap left open; the 2026-10-05 audit's report
/status counted every inbox-*.md for the menu bar's "N items waiting",
while load_inbox skips deferred items and items whose subject is
archived, dropped or gone (G98). On the live bank the menu bar said 49
while the inbox listed 35.

One predicate (inbox_service._hidden) now decides whether a stored item
is served; load_inbox and the new inbox_service.served_counts both ask
it. /status and the hub's pending count read served_counts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PWFmGTJTSBe2Qd8aAJE2Qd
fix(inbox): the menu bar counts what the inbox serves
The help icon and every other iconHover glyph outside the rail played SF
Symbols' per-layer .wiggle, whose strength cannot be set; the owner found
it far too much. IconHover now has one hover motion, the rail's 1 → 1.05
→ 1 grow-and-settle with no rotation, and the subtle: switch is gone so
the two cannot diverge again. The selected: bounce is unchanged.

DR-64 gains the rule; DESIGN_RULES carries the dated 2026-10-06 ruling.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PWFmGTJTSBe2Qd8aAJE2Qd
fix(app): every glyph hovers like the rail — one small nod, no wiggle
Codex's 2026-10-06 audit of all G addresses against dev: per-ID
implementation ledger (BACKLOG_IMPLEMENTATION_REVIEW.md), completed
scopes moved to DONE.md, original prompts kept in BACKLOG_CONTEXT.md,
and TODO.md reordered (G182 releases today; G183/G177/G185 P0; G10
trial, G110 continuity, G104 research, G148 rebuild, G179 P1).
New specs: project continuity, start prompts, provider/ingestion
modularity, release + integration acceptance, benchmark rebuild.

Adds G189 (Home revamp; painted banner off behind one constant now)
and G190 (Claude artifacts and ChatGPT attachments in exports).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014BvzEoxAnppDTdnQvi1oLQ
…hes it (G182)

The owner amended the manual-promotion rail for releases only: feature PRs
still target dev; a release is a dev→main PR carrying the VERSION bump, and
merging it makes CI tag, build, verify and publish the GitHub Release.
CLAUDE.md's branch line and working-method's branching rail now say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01G3GQg1xpmgJZK7gwYPxwBq
docs(goals): code-backed backlog review, priority order, ruling 19 (release = dev→main merge)
…182)

agree holds VERSION, pyproject, uv.lock (and a built app's Info.plist and
latest.json) to one value; plan reads the remote's v* tags and says
released (publish nothing), new (publish as latest) or fails loudly when
an untagged VERSION is not greater than the latest tag (ruling 19).

Co-Authored-By: Claude <[email protected]>
…(G182)

Ruling 19: release.yml runs on main (publish), ci/release-dry-run and
workflow_dispatch (dry runs; a manual run on main is the recovery path).
A plan job on ubuntu checks every version stamp and the remote's tags:
already tagged publishes nothing and stays green, not greater fails. The
build job checks the app's Info.plist and latest.json against VERSION and
adds Cicada-macos-arm64.zip (same bytes) for a stable latest/download link.
publish.sh drafts the release at the merged commit, verifies every asset,
then publishes (GitHub creates the tag only then), marking it latest only
when it is the highest; any failure deletes the draft and its tag.

Co-Authored-By: Claude <[email protected]>
…ION (G182)

The release PR check (ruling 19): head is this repo's dev, every version
stamp agrees, and VERSION is untagged and greater than the latest tag —
what release.yml will publish on merge. Ubuntu, no build.

Co-Authored-By: Claude <[email protected]>
…G182)

Ruling 19: `make release VERSION=x.y.z` bumps VERSION, pyproject and
uv.lock on release/vX.Y.Z off the remote's dev and opens its PR to dev;
`make release-pr` opens dev → main ("Release vX.Y.Z"), and merging that
is the release. Both refuse a tagged version or one not greater than the
latest tag (through check_version.py); a VERSION dev already carries needs
no bump. --dry-run pushes and opens nothing. Tested against a bare repo
and a fake gh.

Co-Authored-By: Claude <[email protected]>
…G182)

RELEASING.md: the two commands, what CI does on main, the stable
latest/download link, recovery from a failed run, and why an older-line
hotfix never becomes latest. app.md's releases paragraph and README's
branch line follow ruling 19.

Co-Authored-By: Claude <[email protected]>
…ncel a release (G182)

judge's failure inside [ "$(…)" ] did not trip errexit, so a version
below the latest tag but above dev's would have been bumped; it is now an
assignment. Dry-run pushes queue in their own concurrency group, since
GitHub cancels a pending run when a newer one joins the group. publish.sh
gets GH_REPO explicitly.

Co-Authored-By: Claude <[email protected]>
Exercise every event answer key, note and date-move branch against a synthetic git-backed bank with an existing conflict. Current follow-up event reconciliation emits no conflict nudges; the historical merged-conflict manifest defect does not reproduce. Preserve the tests and document the current owned-path commits without changing the manifest implementation.
…stale plan (G182)

Review finding: "Re-run failed jobs" reuses the plan job's outputs, so
after a newer release an older VERSION could have been published and
marked latest. publish.sh now reads the remote's tags itself through
check_version.py (already tagged → green no-op; not greater → fail) and
takes latest/previous from that. A failed gh lookup is no longer read as
"no release"; the cleanup deletes only the draft (a draft has no tag, so
--cleanup-tag could only ever delete someone else's); the build artifact
upload overwrites on a full re-run; the concurrency comment and docs say
what GitHub really does with a pending run.

Co-Authored-By: Claude <[email protected]>
Snapshot episode hash matches before taking episode_lock, then validate file identity and metadata under the lock and read only new or changed text before exclusive creation. Detect replacements and same-size edits even when mtime is restored. Preserve scrubbed hashes, max-suffix ids and remote commit behavior; cover raced duplicates and simultaneous saves.
feat(app): the painted Home band is off behind HomeBandLayout.showsBand (G189a)
test(api): deflake date and source scans (G185)
Exercise agent-over-person milestone divergence through the real MCP advance and scoped commit, and assert that event reconciliation never emits mergeable conflict nudges. Require reconciliation calls for event answers and retain ownership checks alone for withdrawal. Clarify the event-wide non-reproduction and revisit trigger, plus the signature check atomic-write assumption.
Review blocker: an old run whose commit was superseded on main, with the
same VERSION still untagged, could publish the old code and leave the
fix's run a green no-op. publish.sh now checks git ls-remote's main
before creating the draft and again just before publishing; a superseded
run ends green ("superseded by <sha> — that run releases main") and
deletes only the draft it made. If two release merges land before the
first publishes, only the newer ships.

Runs for different commits may now overlap, so releases are addressed by
id through gh api, never by tag (GitHub lets two drafts share a tag):
this run's draft is the one with this tag and this commit as target.

Co-Authored-By: Claude <[email protected]>
…at a re-run can do (G182)

A shared release group let any newcomer — a re-run of an old commit
included — replace the pending run for a newer release, even with
cancel-in-progress false. Each main commit now has its own group, so no
run cancels another commit's; across commits publish.sh's tip and
live-tag checks decide. Dry runs keep per-ref groups. RELEASING.md: a
re-run publishes only while main's tip is still its commit; a fix ships
as a new release PR, which may keep the same untagged VERSION.

Co-Authored-By: Claude <[email protected]>
fix(capture): save_episode scans outside episode_lock; follow-up/event commit ownership proven (G183 b, c)
…he tag spelled out and checked (G182)

Re-review findings:
- The publication PATCH omitted tag_name, which drops a release's tag
  (the GitHub CLI guards against exactly this), and the read-back only
  checked draft=false. The PATCH now sends tag_name, target_commitish,
  draft=false and make_latest; afterwards the release must read back
  public under vX.Y.Z and the remote's real tag (peeled) must be the
  run's commit — otherwise the run fails loudly, retags and overwrites
  nothing, and says what to check by hand.
- The new draft was rediscovered through the releases list, which lags.
  It is now created with POST releases (body = header + generate-notes
  since the previous tag) and owned by the id in that response; assets
  upload to that id's uploads URL and the draft is read back by id (tag,
  target, every asset's size) before publication. Cleanup deletes only
  that id, and only while it is still a draft. The list is read once, for
  a published release and this commit's leftover drafts.

The fake gh (api/tests/_release_fake_gh.py) now models GitHub honestly:
an omitted tag_name drops the tag, the list may lag, uploads go by id,
publication creates the tag unless one exists.

Co-Authored-By: Claude <[email protected]>
feat(release): a merge to main is a release — CI tags VERSION and publishes (G182, ruling 19)
@rorosaga
rorosaga merged commit 87de197 into main Oct 6, 2026
1 check 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.

1 participant