<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pearl/docs, branch main</title>
<subtitle>Pearl Edits and Reflects Linear — manage Linear.app issues as org-mode in Emacs
</subtitle>
<id>https://git.cjennings.net/pearl/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/pearl/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/'/>
<updated>2026-06-24T04:08:58+00:00</updated>
<entry>
<title>docs: spec for refining the current source</title>
<updated>2026-06-24T04:08:58+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T04:08:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=59cd35b9645cbe10a5e51ea6110bf8b2c382a7b6'/>
<id>urn:sha1:59cd35b9645cbe10a5e51ea6110bf8b2c382a7b6</id>
<content type='text'>
A spec for pearl-refine-current-source: mutate the active source via a refinements stack so a narrow sticks across refresh, re-fetch server-side for both filter and view sources, and factor a one-dimension reader out of the builder. Three design questions left open for sign-off before build.
</content>
</entry>
<entry>
<title>docs(spec): add the modified-ticket indicator spec</title>
<updated>2026-06-07T11:17:34+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-07T11:17:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=24a84aa854917f7098631c3b1d3c735eab443731'/>
<id>urn:sha1:24a84aa854917f7098631c3b1d3c735eab443731</id>
<content type='text'>
Plan for surfacing unsaved local edits under the deferred save model. The cue is a background highlight (pearl-modified-highlight, inheriting diff-changed) over the field that changed (title, description, comment, or tag), plus the heading for fold-visibility, and a mode-line count of tickets with unsaved pushable work. It rides the existing dirty scan and viewer classification, so there's no new dirty logic, and it never mutates the buffer.

Two Codex review rounds and a follow-up design pass are folded in: the comment cue separates pushable own edits from non-pushable read-only ones, the update lifecycle and a single redecorate entry point are specified, and a cross-theme legibility survey picked diff-changed over diff-refine-changed.
</content>
</entry>
<entry>
<title>docs: spec for interactive grouping of the current view</title>
<updated>2026-06-06T19:26:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T19:26:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=6a1d2e9085d6b247e72b4c98e776aa9fb128341f'/>
<id>urn:sha1:6a1d2e9085d6b247e72b4c98e776aa9fb128341f</id>
<content type='text'>
A sibling to the interactive-sort spec: group the buffer you're looking at on demand, without configuring the grouping in Linear and re-fetching. pearl-set-grouping completing-reads a dimension (status, project, assignee, priority, or none) and regroups client-side, since those four dimensions are already in each issue's drawer.

The crux is preserving unsaved edits while the outline level changes. Grouping moves an issue from level 2 to level 3 under a group heading, so the regroup moves each subtree byte-for-byte and shifts only its heading stars. That's the edit-preserving move sort uses, plus a level delta. The interactive choice persists under :client-group, beside a view's own :group. A single pearl--effective-grouping resolver feeds every render and merge path, so a refresh reproduces what's on screen.

Cycle is deferred: it isn't rendered into the drawer, so an offline regroup can't read it. A separate task adds cycle drawer fields and re-enables it.
</content>
</entry>
<entry>
<title>docs: spec for a configurable default view</title>
<updated>2026-06-02T17:51:19+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T17:51:19+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=293c9f2a097991ca64f05f6252d0725f510d2e47'/>
<id>urn:sha1:293c9f2a097991ca64f05f6252d0725f510d2e47</id>
<content type='text'>
The hot key l is hardwired to my-open-issues. This spec lets l open a user-chosen default instead, defaulting to my-open-issues so nothing changes until you set one.

Four decisions settled before code. The value is nil or a local-view name (default to a Linear view by saving it locally first). The default is per-account when accounts are configured, because local views are account-stamped and a global default would refuse to run cross-account. A new pearl-open-default-view takes l, and pearl-list-issues stays literal at f o. The setter persists through customize-save-variable, and it's the v D slot the keybinding work reserved.

Ready after a Codex round: the transient set-default-view key is . since D is taken by save-locally, and a test-surface task was added.
</content>
</entry>
<entry>
<title>docs(filter): document multi-state selection and reconcile the views boundary</title>
<updated>2026-06-02T04:50:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T04:50:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=93c70b2b42174caa101d3de1fc46630dcdbcf941'/>
<id>urn:sha1:93c70b2b42174caa101d3de1fc46630dcdbcf941</id>
<content type='text'>
I documented the new multi-state capability and brought the views spec and README back in sync with it. The README's filter section notes the State prompt is multi-select and shows the (:state ("Todo" "In Review")) form.

The local/Linear views spec's reverse-compile dimension table, singular-vs-plural rule, and refusal list now list state.name.in as a plural-capable exception alongside :labels and :state-type, and the README copy-down paragraph no longer claims a multi-value filter on any single dimension refuses. The views vNext note records that the named-states case shipped here.

Phase 3 of docs/multi-state-filter-spec.org. Docs only.
</content>
</entry>
<entry>
<title>docs: spec for selecting multiple statuses in a filter</title>
<updated>2026-06-02T04:33:46+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T04:33:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=5ef986c880fa6fdfafe3f11943524119065bd044'/>
<id>urn:sha1:5ef986c880fa6fdfafe3f11943524119065bd044</id>
<content type='text'>
I wrote a spec for letting a filter match several workflow states at once. The builder picks one state today, so "Todo or In Review" can't be built. The design makes :state polymorphic, a name or a list of names, mirroring how :state-type already works, so (:state ("Todo" "In Review")) compiles to state.name.in.

It covers the four filter-side readers (compiler, builder, validator, reverse-compile), reconciles the views spec's representability boundary, and notes the issue's own state field is a separate, unaffected use. Codex review incorporated. Status Ready.
</content>
</entry>
<entry>
<title>feat(views): reshape the keymap and transient around the view noun</title>
<updated>2026-06-01T22:53:53+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T22:53:53+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=ad1e0b8c33f909814b69458eb34cc792f032e844'/>
<id>urn:sha1:ad1e0b8c33f909814b69458eb34cc792f032e844</id>
<content type='text'>
I put the view operations under the verb a user would reach for. Create, edit, and delete local views are c v, e v, and k v. The fetch group gains run-local-view at f l and keeps run-linear-view at f v, with publish at f u and save-locally reserved at f d. Since f p already runs issues by project, publish and save-locally take u and d, which read as up and down and mirror the copy-up and copy-down lifecycle.

The transient now has a Views column grouping create, edit, run-local, run-linear, publish, and delete, separate from the source-fetching commands. I updated the spec's keymap to the implemented letters so the two stay in sync.

Phase 3 of the views spec. Keymap tests assert the new bindings. Suite, compile, and lint are green.
</content>
</entry>
<entry>
<title>docs: finalize local/Linear views spec for implementation</title>
<updated>2026-06-01T22:08:36+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T22:08:36+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=4eab7a048ca18eeef31e4f8d0823182fc6e2368f'/>
<id>urn:sha1:4eab7a048ca18eeef31e4f8d0823182fc6e2368f</id>
<content type='text'>
The spec is implementation-ready. It pins the design the earlier drafts left open, across the parts that turned out to need real contracts rather than prose.

Copy-down's reverse-compile is the load-bearing piece. A live probe verified that CustomView.filterData is readable but stored as Linear's and/or view-filter tree, not the IssueFilter shape Pearl's compiler emits, so the reverse-compile is a normalize-then-match: flatten the tree, unwrap single-branch or/and, read one-element in as a scalar, then match each conjunct to a Pearl authoring key. The per-dimension contract is explicit, including the cases where the compiled filter loses information: :state and :state-type stay distinct, :priority canonicalizes to the integer, :open t maps from the exact open-state nin while generic nin refuses, and a multi-value in on any singular key refuses rather than silently narrow the view. Anything outside the model refuses with a structured reason instead of producing a lossy local view.

The rest of the surface is settled too. A tracked local view runs the local filter, the source of truth, not its Linear mirror. Edit preserves all non-edited metadata through a dedicated writer. One account guard covers every read and mutate, with delete and publish refusing before any Linear call on a mismatch. Local and cross-store name collisions have a defined policy. Vocabulary is direct-renamed to filter / local view / Linear view, with intent-first publish and save-locally commands and no aliases. Agreed decisions, acceptance criteria, a phased plan, and the dispositions are recorded inline.
</content>
</entry>
<entry>
<title>docs: add bidirectional copy to the local/Linear views spec</title>
<updated>2026-06-01T20:03:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T20:03:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=55fa2ffe2527ab6a9d93da21b153b26bc803ffda'/>
<id>urn:sha1:55fa2ffe2527ab6a9d93da21b153b26bc803ffda</id>
<content type='text'>
I expanded the spec to make copying a view between the two stores a first-class operation in both directions, and deliberately asymmetric. Copy up (local view to Linear view) is publish: it creates a tracked Linear view, and re-copying updates it in place. Copy down (Linear view to local view) is a fork: it duplicates the Linear view's filter into a new, independent local view you can edit and rename, with no sync link back. The principle behind the asymmetry is that the local view is the editable source of truth.

Copy down is the one direction that needs real engine work. Because the result has to be editable, it can't store opaque filter JSON. It has to reverse-compile Linear's IssueFilter back into Pearl's authoring plist. The spec carries the dimension table the reverse-compile inverts, and draws the representability boundary: when a Linear view's filter uses OR logic, an unmodeled dimension, or an operator Pearl doesn't emit, copy down refuses with a clear message rather than dropping conditions silently. A local view that looks right but matches a different issue set, diverging on every refresh, is the worse failure.

I also added the two decisions the change forces (refuse-don't-guess on unrepresentable filters, fork-not-track on copy down), the copy-up naming question, keymap slots, acceptance criteria for both round-trips, and two implementation phases. The spec is still a Draft, now with eight open questions.
</content>
</entry>
<entry>
<title>docs: spec for local views and Linear views</title>
<updated>2026-06-01T19:15:28+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T19:15:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=42370043d61b7f8a80a17785f47df359e4f70dda'/>
<id>urn:sha1:42370043d61b7f8a80a17785f47df359e4f70dda</id>
<content type='text'>
I drafted a spec that renames "saved query" to one noun with a location qualifier: a local view (private, on disk) and a Linear view (published, shared, the CustomView Linear users already know). The lifecycle is git-shaped: a local view is a local branch, a Linear view a remote branch, publish is push, and the stored view-id is the tracking link.

The model already exists in the code. What's missing is a coherent name and command shape. Today the saved-query operations scatter across the verb-first keymap: create is hidden inside the filter builder and filed under fetch, delete has a home, edit doesn't exist. The spec covers the rename map (obsolete aliases so no init.el breaks), new create and edit commands, the keymap/transient/picker reshape, and the config migration. One piece isn't a rename: pulling a Linear view back into a local view needs a reverse-compile of IssueFilter, deferred to vNext.

Status is Draft with six open questions. No code changed.
</content>
</entry>
</feed>
