<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pearl/tests/test-pearl-reverse-compile.el, 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-17T04:02:13+00:00</updated>
<entry>
<title>fix(views): accept Linear's open filter when copying a view down</title>
<updated>2026-06-17T04:02:13+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-17T04:02:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=8061ec3d83fdd9896f84e4d8590eb878277e4949'/>
<id>urn:sha1:8061ec3d83fdd9896f84e4d8590eb878277e4949</id>
<content type='text'>
The copy-down reverse-compiler only treated state.type nin [completed, canceled, duplicate] (Pearl's own :open predicate) as open. Linear's UI authors open as nin [completed, canceled], without duplicate, so copying down any standard open-issues view refused with "state.type uses a nin that isn't Pearl's open predicate" and never reached the save.

A nin now maps to :open when every excluded type is a closed type and the set excludes both completed and canceled. That covers Linear's two-type form and Pearl's three-type form. A nin missing one of the two, or one that also excludes an open type like backlog, still refuses.
</content>
</entry>
<entry>
<title>fix(views): save a local view from any Custom View, not just favorites</title>
<updated>2026-06-16T21:49:08+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-16T21:49:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=0ab4afcd1e4e336cb69bd983e498bc441828ed5b'/>
<id>urn:sha1:0ab4afcd1e4e336cb69bd983e498bc441828ed5b</id>
<content type='text'>
pearl-save-linear-view-locally only listed favorited Linear views, while pearl-run-linear-view lists every Custom View in the workspace. A view you could run often wasn't offered to save, so the copy-down silently produced nothing. The command now saves the current buffer's view when one is showing, and otherwise prompts over all Custom Views.

Favorited custom views also came through nameless: the favorites query fetched customView id but not name, and Linear leaves the favorite's title blank for entity favorites. The picker showed a blank entry and the save dead-ended at a blank name. The query now selects customView name and pearl--normalize-favorite falls back to it, which also fixes blank names in pearl-pick-source.

The success message now names the saved view and how to run it or set it as the default.
</content>
</entry>
<entry>
<title>feat(filter): :state accepts a list of state names</title>
<updated>2026-06-02T04:40:11+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-02T04:40:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=8bae955521cdf3e2f8d5ff015b3ae36b5b8a75e8'/>
<id>urn:sha1:8bae955521cdf3e2f8d5ff015b3ae36b5b8a75e8</id>
<content type='text'>
I made :state polymorphic, mirroring :state-type. A string still compiles to state.name.eq. A list now compiles to state.name.in, so (:state ("Todo" "In Review")) matches issues in either state. The reverse-compile inverts state.name.in back to a scalar (one name) or a list (several), so copy-down no longer refuses a Linear view filtering on a set of named states. The validator accepts a string or a list of non-empty names, the same shape it already enforces for :labels, and a list-valued :state keeps its precedence over :state-type and :open.

Phase 1 of docs/multi-state-filter-spec.org. Suite, compile, and lint green.
</content>
</entry>
<entry>
<title>feat(views): save a Linear view locally via the reverse-compile (copy down)</title>
<updated>2026-06-01T23:21:47+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-01T23:21:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=9b9a264edb0c208892fcfe5101912d00858de766'/>
<id>urn:sha1:9b9a264edb0c208892fcfe5101912d00858de766</id>
<content type='text'>
I implemented copy-down, the one direction that needed real engine work. A live probe showed CustomView.filterData is readable but stored as Linear's and/or view-filter tree, not the flat IssueFilter Pearl emits, so pearl--reverse-compile-issue-filter is a normalize-then-match: it flattens the top-level and, unwraps single-branch and/or, reads a one-element in as a scalar, then matches each conjunct to one authoring key. Every helper returns (ok . plist) or (refuse . reason).

The contract follows the spec's dimension table: distinct :state and :state-type, canonical numeric :priority, :open t mapped 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 Pearl's AND-only model refuses with a structured reason that names the construct.

pearl-save-linear-view-locally lists favorited Linear views, fetches the chosen view's filterData through a small async helper, reverse-compiles it, and saves a forked local view through pearl--save-local-view: no tracking link, :copied-from-view-id provenance only, active account stamped. On a refusal it points the user at pearl-run-linear-view; on success it states the fork's independence. Bound at f d (down) and the transient Views column.

Phase 6 of the views spec. 30 tests cover per-dimension round-trips (build-&gt;encode-&gt;read bridge), normalize-from-real-JSON trees, the full refusal set, and the command-side. Suite, compile, and lint are green.
</content>
</entry>
</feed>
