<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pearl/tests, 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-24T03:40:27+00:00</updated>
<entry>
<title>feat(sources): single-issue source (favorites + open-issue-by-id)</title>
<updated>2026-06-24T03:40:27+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-24T03:40:27+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=8869a083c3cc28c20efa63e8932347329b2865f4'/>
<id>urn:sha1:8869a083c3cc28c20efa63e8932347329b2865f4</id>
<content type='text'>
I wired the issue kind into the same pipeline every other source uses, so a favorited issue renders in the buffer as its own subtree instead of the v1 browser punt. The new pearl-open-issue-by-id command does the same for an issue you type by identifier or id. It's handy when someone hands you ENG-123 and you'd rather read it in Pearl than a browser.

Linear's issue(id:) accepts both the UUID and the human identifier, so there's no resolution step. I fetch by whatever was given and build the :type issue source from the returned node, so the stored id and identifier stay authoritative. Refresh re-fetches the one issue and merges by LINEAR-ID like the filter and view sources.

Tests cover the source constructor, the header round-trip, and the fetch/render and command paths across Normal, Boundary, and Error.
</content>
</entry>
<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>fix(api): clear auth-source's negative cache on pearl-clear-cache</title>
<updated>2026-06-16T04:57:14+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-16T04:57:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=546bcf9bbe651eaaa0129346536c9e7e98748555'/>
<id>urn:sha1:546bcf9bbe651eaaa0129346536c9e7e98748555</id>
<content type='text'>
A key added to authinfo read as missing for up to 2h because auth-source caches the negative lookup and `pearl-clear-cache` only cleared pearl's own caches. It now also calls `auth-source-forget-all-cached`, and the auth-source miss tells the user to run `M-x pearl-clear-cache`. This is the 2026-06-01 live incident, where a freshly-synced Linear key kept erroring as unset.
</content>
</entry>
<entry>
<title>fix(api): signal missing-key as user-error with mode-aware advice</title>
<updated>2026-06-16T04:53:57+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-16T04:53:57+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=e749badfd974de42fe8d7cdbe2bfa4fa3e0771b2'/>
<id>urn:sha1:e749badfd974de42fe8d7cdbe2bfa4fa3e0771b2</id>
<content type='text'>
A missing API key raised a raw `error`, so it backtraced under `debug-on-error`, and the stale advice pointed at customizing `pearl-api-key`. Now `pearl--headers` signals a `user-error` and branches the advice by mode: accounts mode points at `pearl-accounts` and `pearl-switch-account`, legacy mode at `pearl-api-key` and `pearl-load-api-key-from-env`. The credential-source resolver's three failure paths become `user-error` too, since a bad credential is a config problem, not a bug.
</content>
</entry>
<entry>
<title>style(render): space the account name from Pearl in the lighter</title>
<updated>2026-06-07T13:07:55+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-07T13:07:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=95209466881d3e6fd4f0d097358b16842d83ec6d'/>
<id>urn:sha1:95209466881d3e6fd4f0d097358b16842d83ec6d</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(render): highlight unsaved comments and update the cues live</title>
<updated>2026-06-07T11:43:17+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-07T11:43:17+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=e2208276021def42dadd5e422aa96e9004040dd9'/>
<id>urn:sha1:e2208276021def42dadd5e422aa96e9004040dd9</id>
<content type='text'>
Completes the modified-ticket indicator (docs/modified-ticket-indicator-spec.org) on top of the field highlight and count.

A changed comment now gets its own highlight by ownership: your own comment (pushable) takes pearl-modified-highlight and bubbles up to the ticket heading; a non-own comment, which save skips, takes the muted pearl-modified-local; a comment seen before the viewer resolves takes the neutral pearl-modified-unknown until classification lands. The viewer is resolved only when a comment is actually dirty, so opening or editing a clean or field-only buffer makes no network call.

The cues update live: a buffer-local after-change hook debounces a redecorate on an idle timer (pearl-modified-idle-delay), so an edit shows within a fraction of a second instead of waiting for a save or refresh. The timer is cancelled when pearl-mode turns off or the buffer is killed. One pearl--redecorate-modified now drives the field highlights, the comment classification, and the count from a single scan, called on mode enable, fetch/refresh, and the idle tick.
</content>
</entry>
<entry>
<title>feat(render): show unsaved tickets with a field highlight and a count</title>
<updated>2026-06-07T11:31:05+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-07T11:31:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=c740e270721581120cc6e9317ab70dba40156820'/>
<id>urn:sha1:c740e270721581120cc6e9317ab70dba40156820</id>
<content type='text'>
Under the deferred save model a ticket can carry unsaved local edits with nothing on screen saying so. These are the first two phases of the modified-ticket indicator (docs/modified-ticket-indicator-spec.org).

A pushable-dirty ticket gets a background highlight (pearl-modified-highlight, inheriting the theme's diff-changed) on its heading line, and on the description body when the description changed. The mode-line lighter gains a count rendered with the ticket glyph: "Pearl[work] 3 🎫 changed". Both ride the existing dirty scan and redecoration pass, clear on the next pass after a save, and never touch buffer text.

The count is tickets, not fields: each ticket counts once however many of its fields changed, and only when it has pushable work (a dirty field, or an own comment once the viewer resolves), so it matches what save-all would push. Per-comment highlighting and the live after-change trigger come next.

diff-changed was picked over diff-refine-changed after a cross-theme legibility survey; pearl now requires diff-mode for that face.
</content>
</entry>
<entry>
<title>fix(keymap): label the issue create/delete keys 'ticket' to match the t chord</title>
<updated>2026-06-07T06:00:55+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-07T06:00:55+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=865473ebe470d4abc858c67ba1456c10ca0012c9'/>
<id>urn:sha1:865473ebe470d4abc858c67ba1456c10ca0012c9</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(render): name the active account in the buffer title</title>
<updated>2026-06-06T23:33:10+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-06-06T23:33:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/pearl/commit/?id=4a4b1bc974a3fdb4d88af3ee575786ca363696e2'/>
<id>urn:sha1:4a4b1bc974a3fdb4d88af3ee575786ca363696e2</id>
<content type='text'>
With two workspaces, the preamble summary read "Pearl - &lt;view&gt; ..." for both, so you couldn't tell from the title which account a buffer belonged to. It now reads "Pearl - &lt;account&gt; - &lt;view&gt; · &lt;count&gt; · &lt;time&gt;", pulling the account from the buffer's #+LINEAR-ACCOUNT. A legacy buffer with no account stamp drops the segment and reads as before.
</content>
</entry>
</feed>
