aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* build: resolve test-file to the exact name before globbingCraig Jennings2026-05-251-1/+9
| | | | make test-file FILE=test-pearl-save grabbed test-pearl-saved.el — the glob *FILE*.el matched both and head -1 picked one arbitrarily. It now prefers an exact FILE or FILE.el match, and errors with the candidate list when a bare substring matches more than one file, instead of silently running the wrong one.
* feat(save): write synced baselines for structured fieldsCraig Jennings2026-05-252-0/+32
| | | | | | Save-model v2 reconciles a field by diffing its live value against a baseline last synced from Linear, but the structured fields (priority, state, assignee, labels) only stored their current value — there was nothing to diff against. The renderer now writes a synced baseline beside each: LINEAR-PRIORITY (the numeric priority), LINEAR-STATE-ID-SYNCED, LINEAR-ASSIGNEE-ID-SYNCED, and LINEAR-LABEL-IDS / LINEAR-LABEL-IDS-SYNCED (the live and baseline id sets, with LINEAR-LABELS staying display-only names). At fetch, live equals baseline, so nothing reads dirty on a fresh issue. The merge refresh re-renders changed subtrees through the same renderer, so baselines advance there too, and a retained dirty subtree is left untouched.
* docs: add the ticket save-model v2 specCraig Jennings2026-05-252-0/+229
| | | | | | The v2 spec folds the structured fields — priority, state, assignee, labels — onto the save engine and removes immediate push, so the package has one write path instead of two. It extends the shipped v1 save-model spec and supersedes the "field setters stay immediate" half of v1 decision 8. I incorporated two Codex review rounds. I also added the supersession pointer to the top of the v1 spec so the v1/v2 relationship is legible from either file.
* refactor: rename issue-body tests to match entry-body-at-pointCraig Jennings2026-05-251-5/+5
|
* feat: render inline code spans verbatim in the Org bufferCraig Jennings2026-05-252-36/+75
| | | | | | | | A fetched description with markdown inside backticks — `**bold**`, `[text](url)`, `_italic_` — got its span contents converted along with the surrounding prose, so the Org buffer showed `*bold*` and `[[url][text]]` inside what should read as literal code. Markdown treats a code span as opaque; the converter didn't. Both converters now tokenize code spans first and apply link/bold/italic conversion only to the text between them, keeping span contents literal. The md->org and org->md directions each split into an emphasis-only helper (pearl--md-emphasis-to-org, pearl--org-emphasis-to-md) called on the gaps, with the span passed through untouched. Keeping the split symmetric is what preserves the round-trip identity a fetch + no-edit push depends on. A new test asserts the verbatim rendering for bold, link, and italic inside a span, plus emphasis still converting outside one and between two spans.
* refactor: rename pearl--issue-body-at-point to pearl--entry-body-at-pointCraig Jennings2026-05-256-27/+28
| | | | The helper returns the body of the Org entry at point, before any child heading. It started out reading issue descriptions, but comment editing and deletion reuse it for comment bodies too, so "issue" in the name was misleading. I renamed it across its callers and the sync/refresh/save/comment tests, and reworded the docstring to say "entry".
* refactor: drop the now-dead issue-id guards in the field settersCraig Jennings2026-05-251-6/+0
| | | | pearl-set-state, pearl-set-assignee, and pearl-set-labels each kept an (unless issue-id (user-error ...)) check that became unreachable once they switched to pearl--goto-issue-heading-or-error, which already signals when no enclosing issue heading carries LINEAR-ID. I removed the three dead guards; the climbing guard is the single source of that error now.
* fix: guard issue async callbacks against a killed bufferCraig Jennings2026-05-252-12/+45
| | | | | | pearl--push-issue-field (the field setters) and pearl-refresh-current-issue ran goto-char on the captured marker in their success callbacks with no liveness check. If the buffer was killed before the async response returned, the dead marker signaled "Marker does not point anywhere" from the callback. I wrapped the buffer work in a buffer-live-p check; the field setters still report the remote success, and refresh reports that the buffer was closed. This closes the last gap in the command/context test-coverage task. The other seven items it named were already covered by the quick-bug batch's tests; this adds the killed-buffer regression for the issue paths (delete-current-comment already had its own).
* docs: record the WorkflowState.position verification in the TODO-keywords specCraig Jennings2026-05-251-2/+6
| | | | I ran the live check the spec named as its one prerequisite: WorkflowState.position exists and returns a float (Dev Review 947.14, In Progress 2, Planning 0), so the per-team position ordering works as designed and the no-position fallback is unnecessary. Recorded it in the status and prerequisites and folded in the round-5 review (the only finding was that status/rubric hygiene). The spec is now cleanly READY with no open prerequisites.
* fix: seven quick bugs from the 2026-05-25 code reviewCraig Jennings2026-05-252-47/+255
| | | | | | | | | | | | | | | | | | A batch of small, independent fixes a code-review pass turned up, each its own :bug:quick:solo: todo. They share no code path beyond pearl.el, so the body lists them. Single-issue refresh false-stash: pearl-refresh-current-issue decided whether to stash "local edits" by hashing org->md against LINEAR-DESC-SHA256, the lossy markdown round-trip. A clean issue whose description has lossy markdown (a # heading, single-asterisk italics) looked dirty and got stashed before every refresh. It now uses pearl--subtree-dirty-p, the Org-hash-first check the merge refresh already uses. Comment outcome parent id: pearl--save-comment-field read :issue-id from the comment heading, which carries LINEAR-COMMENT-ID, not LINEAR-ID, so comment outcomes got :issue-id nil against the contract. A new pearl--issue-id-at-point climbs to the enclosing issue. save-all viewer-unavailable count: when the viewer lookup failed, save-all passed a nil viewer id and the prompt counted every dirty comment as read-only, disagreeing with the actual skipped/viewer-unavailable outcome. The counts and prompt now carry an explicit viewer-unavailable tally. Request-counter leak: pearl--graphql-request-async incremented pearl--active-requests before pearl--headers, which signals when the key is unset, so neither callback ran to decrement it. I build the headers before the increment. Issue commands from comment subtrees: save-issue, refresh, compose-description, open-issue, and the field setters read LINEAR-ID from the nearest heading, so running them from inside a comment hit the comment heading and rejected with "Not on a Linear issue heading". A new pearl--goto-issue-heading-or-error climbs to the issue heading; the commands use it. Project selector on no projects: pearl-select-project ran (string= selected "None") even when a team had no projects and selected was nil. I guard the nil case. (The reported wrong-type crash doesn't reproduce here, since string= treats nil as the symbol "nil", but the guard makes the optional-no-projects path explicit.) Query filter validation: pearl-run-saved-query, pearl-list-issues-filtered, and pearl-list-issues compiled their authoring plist without calling pearl--validate-issue-filter, so an unknown key in a saved query was silently ignored. They now validate at the command boundary, so typos surface as a clear user-error.
* feat: add pearl-delete-current-commentCraig Jennings2026-05-254-6/+318
| | | | | | | | | | Pearl could render, create, and edit comments but not delete one, so removing your own comment meant leaving Emacs for the web app. I added the delete command and filled the reserved C-; L d c keymap slot. pearl-delete-current-comment runs from inside a comment's subtree. It's own-only, reusing the viewer gate from pearl-edit-current-comment: a comment authored by someone else, a bot, or an integration is refused with no commentDelete call. It confirms before the destructive mutation, and on success removes that comment's Org subtree, leaving sibling comments and the issue body untouched. pearl--delete-comment-async mirrors pearl--delete-issue-async. The delete is permanent. I verified Linear's commentDelete against the live API: the comment is not found immediately after, with no restore path, so unlike issueDelete this isn't a recoverable soft delete. Because of that, a comment with unsaved local edits (or no stored hash) gets a stronger "discard your local edits" confirmation rather than the plain prompt. I surfaced it as K in the transient Delete group and as d c in the prefix keymap, and inverted the keymap test that asserted d c was unbound.
* docs: incorporate the spec reviews and move four specs to ReadyCraig Jennings2026-05-254-104/+411
| | | | | | | | | | | | I folded the Codex reviews into four draft specs and brought each to Ready. The reviews surfaced real failure modes, not nits, so the changes are substantive. The comment-deletion spec is Ready: I verified Linear's commentDelete contract against the live API. The mutation is commentDelete(id: String!) returning success, and the comment is not found immediately after, so there's no restore path and undo is impossible. The dirty-local-comment policy is allow-with-discard-wording, the prompt names both the remote delete and the local removal, and the d c keymap / K transient slots are settled. The multi-account spec is Ready. The original "set the globals on switch and let downstream stay oblivious" model leaked across accounts in the async code, so it's redesigned around an account-context layer: dispatch-time context snapshots, #+LINEAR-ACCOUNT file ownership with wrong/unmarked-buffer guards, a runtime (not persisted) active-account, a mode-line indicator, and an exact startup rule. The five safety calls are settled as the safer defaults. The sort-order spec is Ready. Client-side sort now moves whole issue subtrees by LINEAR-ID byte-for-byte rather than reparsing and rewriting, which would have dropped unsaved edits. Header persistence is atomic with the reorder, and Custom Views refuse server-side sort in v1 since customView.issues has no verified orderBy. The labels-as-org-tags spec is Ready: collisions render the shared tag once with the drawer authoritative, Pearl owns the whole issue-heading tag set, manual tag edits are overwritten and never pushed, and Unicode alphanumerics are preserved.
* feat: add the pearl-prefix-map keymap and regroup the transient menuCraig Jennings2026-05-254-33/+206
| | | | | | | | | | The command surface had no muscle-memory path. Everything went through M-x or the transient, and the transient grouped commands by an ad-hoc mix (issue-at-point, create, org-sync) rather than by what you're doing. I added pearl-prefix-map, an opt-in prefix keymap organized as save / edit / new / delete. It isn't bound at load, because a global multi-key prefix isn't reliably free across terminals and GUIs. Each binding is a (label . command) menu item, so which-key shows the label without pearl depending on which-key. The user binds the map to a free prefix (the README suggests C-; L). So C-; L s s saves the ticket at point, s a saves the whole file, e p edits priority, n t creates a ticket, m opens the transient. I regrouped the transient to mirror those categories: Save, Edit, New, Delete for ticket actions, and Fetch, View, Setup for the workspace. I added the two save commands and relabeled every entry to the verb wording. The per-field push entries (edit-desc, edit-title) are gone since the save model subsumes them, though the commands still work from M-x. I pulled the org-sync commands (enable, disable, push-file) from the menu too, since they're plumbing, tracked separately for going private. The keymap binds the existing command names. Aligning those names with the labels (edit-state, new-comment) is a separate task, as is the delete-comment command that will fill the d c slot.
* feat: add the unified ticket save model with save-issue and save-allCraig Jennings2026-05-253-169/+783
| | | | | | | | | | | | Editing a ticket meant remembering which per-field command to run: pearl-sync-current-issue for the description, pearl-sync-current-issue-title for the heading, pearl-edit-current-comment for a comment. Three fields, three commands, and no single way to edit several fields (or several tickets) and push them at once. I added a layered save engine and two commands over it. pearl--run-field-save does the fetch, the three-way conflict gate, and the push for one field, emitting a single structured outcome instead of messaging from its callback. The three interactive sync commands are now thin wrappers over the per-field savers, so they keep working and gain the outcome. pearl-save-issue diffs the ticket at point and pushes only what changed. pearl-save-all scans the whole file, confirms once naming the field counts, and saves every diff in a sequential pass that continues past a per-ticket conflict. A queue runner keeps at most one conflict-resolution buffer live at a time. I also fixed a correctness bug: a description push now advances both LINEAR-DESC-SHA256 (the markdown the remote gate hashes against) and LINEAR-DESC-ORG-SHA256 (the rendered-Org baseline the local scan uses). Advancing only the markdown hash left the Org hash stale, so the next local scan would flag a just-saved ticket as dirty again. The conflict resolver gained an optional outcome callback so the engine can report cancel, use-local, use-remote, and the deferred smerge commit or abort as distinct outcomes. I removed the now-dead pearl--commit-sync-decision. The keybinding scheme and the transient menu retarget are a separate follow-up. Spec: docs/ticket-save-model-spec.org.
* docs: title-case the section headings in the save-model specCraig Jennings2026-05-241-8/+8
|
* feat: add the local dirty scanners for the ticket save modelCraig Jennings2026-05-242-0/+174
| | | | | | First layer of the unified save model (docs/ticket-save-model-spec.org). pearl--issue-dirty-fields returns which of title, description, and comment candidates changed in the issue subtree at point, with no network call. Description dirtiness reuses pearl--subtree-dirty-p, which hashes the rendered Org against LINEAR-DESC-ORG-SHA256, so a description whose markdown is lossy under org->md isn't falsely flagged. Comment candidates hash org->md of each body against LINEAR-COMMENT-SHA256, matching how pearl-edit-current-comment computes its no-op check. Ownership is a separate phase: pearl--classify-comment-candidates splits candidates into own versus read-only once the viewer id is known, since the local scan can't tell who authored a comment without it. The save-issue and save-all commands that consume these scanners land in the next layers.
* docs: add the ticket-save-model and multi-account specsCraig Jennings2026-05-242-0/+375
| | | | Two design specs for upcoming work. ticket-save-model-spec covers the unified "save the ticket" model — diff title/description/comments against their provenance hashes and push only what changed, with a sequential conflict-aware engine and an opt-in keymap — and went through two review rounds (Codex). multi-account-spec covers switching between work and personal Linear workspaces over the existing globals, with auth-source credentials and per-account cache isolation; it's still pre-review draft.
* feat: title-case the view name in the file titleCraig Jennings2026-05-242-1/+13
| | | | The #+title line now renders the source name in title case, gated on pearl-title-case-headings like the issue headings, so "my open bugs" shows as "Linear — My Open Bugs". pearl--source-name itself stays raw since it also feeds the echo-area refresh messages.
* feat: make comment sort order configurable, newest-first by defaultCraig Jennings2026-05-243-37/+77
| | | | I added pearl-comment-sort-order to choose how an issue's comments render under the Comments heading: newest-first (the default, most recent on top) or oldest-first (chronological, like an email thread). Both the render sort and the add-comment insertion point honor it, so a new comment lands at the top for newest-first and at the bottom for oldest-first.
* feat: compose comments and descriptions in an Org bufferCraig Jennings2026-05-242-18/+291
| | | | | | | | | | The minibuffer is too cramped to write a real comment or description. I added a shared compose buffer: a focused Org buffer with a read-only instructional header at the top (like a git commit template) and an editable body below, where C-c C-c submits and C-c C-k cancels. It's the sibling of the smerge conflict buffer, built the same way. Two commands use it. pearl-add-comment, run interactively, now opens the composer and converts the Org body to Markdown before sending. Called with an explicit body (tests, programmatic callers), it still sends that directly. pearl-compose-current-description is new: it pops the issue's current description into the composer and, on submit, writes it back into the body and syncs through the existing conflict gate. Both work from anywhere inside an issue subtree. The header is genuinely uneditable: read-only text properties, with only the last character rear-nonsticky so the body stays editable while edits inside the header are refused. The body is everything below it, extracted by a marker. I left pearl-new-issue on the minibuffer for now. Wiring its description into the composer means restructuring that long, untested interactive flow to defer the create into the submit callback, which is worth doing on its own rather than riding along here. Filed as a follow-up.
* feat: render recent comments in the bulk issue listCraig Jennings2026-05-244-28/+259
| | | | | | | | A populated list used to look like nothing had comments, because only a single-issue refresh fetched them. The bulk and Custom View queries now pull each issue's most recent comments and render them under the Comments heading, with a marker showing how many are shown against the total: 💬 5/18, or 💬 5/25+ once the count passes the cap. Linear's API has no comment total (no commentCount on Issue, no totalCount on the connection), so I fetch one more than pearl-list-comments-count-cap newest-first: that gives an exact total up to the cap and a "+" beyond it, in a single round trip. pearl-list-comments-shown (default 5) caps how many render. pearl-fetch-comments-in-list (default on) turns the whole thing off to keep the fetch light. The single-issue refresh still shows the full thread, uncapped and unmarked. The marker sits on the Comments heading rather than the issue title, so it stays out of the title-sync hash. I relaxed the append locator to match a marked heading, so adding a comment still finds the existing subtree instead of creating a second one. Verified live: the capped fragment is accepted and the markers render correctly.
* feat: render issue headings with the identifier prefix and title caseCraig Jennings2026-05-245-19/+241
| | | | | | I added two display-only heading tweaks, each a defcustom defaulting on. pearl-show-identifier-in-heading prefixes the title with the Linear identifier (** TODO [#B] SE-401: Fix the Bug). pearl-title-case-headings renders the title in smart title case, keeping minor words lowercase unless first or last and leaving words that already carry an uppercase letter (acronyms, identifiers) untouched. Both stay display-only. The LINEAR-TITLE-SHA256 hash covers the rendered (cased, un-prefixed) title, and the title-sync reader strips the identifier prefix before hashing, so an unedited heading is a no-op and neither the casing nor the prefix ever pushes to Linear. A render-then-read round-trip test locks that invariant.
* docs: capitalize PEARL in the README titleCraig Jennings2026-05-241-1/+1
|
* test: add an end-to-end acceptance flowCraig Jennings2026-05-241-0/+200
| | | | Stubs the single GraphQL chokepoint and drives the full integration path against a temp active file: run a saved query, render with the source header, read the source back, refresh the same source with an in-place merge, switch sources while the buffer is dirty (the edit is preserved, not clobbered), then from inside a rendered issue subtree sync an edited description, add a comment, and set priority. Everything above the wire — filter compilation, normalization, sort, render, header round-trip, merge, the conflict gate, comment append, and the field setter — runs for real, so the query and org-representation layers are exercised together rather than in isolation. 388 tests green (384 unit + 4 integration).
* test: cover sort and render ordering boundary casesCraig Jennings2026-05-241-0/+46
| | | | Added ERT tests for the deterministic-ordering guarantees: title sort folds nil and empty titles to the empty key (ordered first ascending); a nil (none) priority sorts last ascending and first descending; equal keys keep input order ascending and reverse it descending (Emacs sort is stable, then nreverse); and the render boundary sorts by priority after normalization even when a node omits the field. 384 tests green.
* test: cover comment insertion and extraction boundary casesCraig Jennings2026-05-242-0/+49
| | | | Added ERT tests: append targets the current issue's Comments subtree even when a later issue already has its own Comments; add-comment from inside a comment subtree refuses (no issue id at that heading); edit-current-comment refuses a comment missing LINEAR-COMMENT-ID with no network call; and a comment body containing drawer-looking (:note:) text reads back intact. 380 tests green.
* test: lock markdown round-trip identity across boundary casesCraig Jennings2026-05-241-0/+14
| | | | Added a round-trip test for the markdown boundary cases the coverage audit flagged: multiple links on a line, parens inside a URL, inline code whose contents look like markdown, non-ASCII prose, and unclosed / trailing fenced code. All round-trip to identity, so a fetch with no edit cannot change the remote on push. Two cases (paren-URL and markdown-looking inline code) round-trip safely despite an imperfect intermediate Org form, noted in the test and filed as a display-fidelity follow-up. 376 tests green.
* test: cover malformed and unknown source headersCraig Jennings2026-05-241-0/+32
| | | | Added ERT tests for the generated #+LINEAR-SOURCE header: a syntactically malformed source line reads back nil (no reader error) and refresh-current-view surfaces the "no source recorded" user error; a valid-but-unknown :type reaches the "Unknown Linear source type" user error; and a source whose name and filter carry quotes, parens, colons, and non-ASCII round-trips through build-org-content and read-active-source unchanged. 375 tests green.
* test: cover cache and resolver failure casesCraig Jennings2026-05-241-2/+57
| | | | Added ERT tests that the per-team collection cache and the resolvers fail soft: pearl--team-collection returns nil and caches nothing on a malformed (missing nodes) or nil response, so a retry refetches and a later success populates the cache; pearl--resolve-team-id returns nil rather than erroring when its backing fetch fails; pearl--custom-views likewise does not cache nil and recovers on a later call. Also dropped a stale pearl--cache-issues binding from this file's cache-reset macro — that variable was removed earlier. 371 tests green.
* test: cover malformed remote page shapes in the query pagerCraig Jennings2026-05-241-0/+41
| | | | Added ERT tests that the query and pager layer degrades gracefully on bad payloads instead of erroring: a success response missing data.issues yields an empty result; data.customView nil yields a structured result with no Lisp error; a page reporting has-next-page with a nil end-cursor terminates at the max-pages bound rather than looping; and pearl--node-list returns the empty list for non-list or non-vector nodes. 366 tests green.
* test: assert the dirty-buffer path preserves disk and buffer stateCraig Jennings2026-05-241-0/+29
| | | | Extended the dirty-buffer coverage: with known content saved to disk and an unsaved edit on top, pearl--update-org-from-issues leaves the buffer modified with its edit, leaves the on-disk file at the original saved content (not the render, not the buffer text), and still surfaces the dirty buffer per the UX contract. 362 tests green.
* test: cover the delete-failure branch keeping the subtreeCraig Jennings2026-05-241-0/+16
| | | | Added an ERT test that pearl-delete-current-issue, when the confirmation is given but pearl--delete-issue-async returns :success nil, leaves the issue subtree and its sibling intact rather than removing local state on a failed remote delete. The command already behaves correctly. 361 tests green.
* test: cover sync-back push-failure branches at the command levelCraig Jennings2026-05-243-0/+55
| | | | Added ERT tests that the three sync commands leave provenance untouched when the push fails (:success nil): pearl-sync-current-issue keeps LINEAR-DESC-SHA256 and -UPDATED-AT, pearl-sync-current-issue-title keeps LINEAR-TITLE-SHA256, and pearl-edit-current-comment keeps LINEAR-COMMENT-SHA256 — and in each case the edited text stays in the buffer for retry. Each test asserts the push was attempted with the rendered text. The commands already behave correctly; this locks it. 360 tests green.
* test: cover field-setter command failure branchesCraig Jennings2026-05-242-0/+59
| | | | Added ERT tests that the four field setters preserve local org state when the issueUpdate mutation fails (returns :success nil): set-priority keeps the cookie, set-state keeps the TODO keyword and LINEAR-STATE drawer, set-assignee keeps the assignee drawer, set-labels keeps the labels drawer. Each test also asserts the push was attempted with the intended input, so a regression that simply stopped calling the API would still fail rather than pass silently. The commands already behave correctly — this locks it. 357 tests green.
* refactor: extract the label-category picker out of pearl-new-issueCraig Jennings2026-05-241-37/+44
| | | | pearl-new-issue was 129 lines — long but linear (a run of creation prompts), and untested since it is eight interactive reads. I pulled out the one intricate, self-contained piece: the issue-type label selection that groups labels by their " - " category prefix and does the two-stage category-then-label pick. It is now pearl--read-issue-label, called for selected-type. Verbatim move, no logic change; new-issue drops to 93 lines. 353 tests green.
* refactor: share the push-and-report tail of the field settersCraig Jennings2026-05-241-47/+42
| | | | set-priority, set-state, set-assignee, and set-labels each repeated the same tail: push the issueUpdate field, and on success update the heading or drawer at the marker, message, and surface the buffer; on failure, message. I pulled that into pearl--push-issue-field, which takes the fields alist, a success thunk (the per-field buffer update), and the two messages. Each command keeps its own completion source, guards, and id resolution. No behavior change; 353 tests green.
* refactor: share one conflict-gate dispatch across the three sync commandsCraig Jennings2026-05-241-80/+95
| | | | The description, title, and comment syncs each carried the same :noop / :conflict / :push dispatch — only the hash property, the local-text source, the fetch and push functions, the apply step, and the status messages differed. I pulled the dispatch into pearl--commit-sync-decision, which takes a spec plist for the varying parts (description alone advances LINEAR-DESC-UPDATED-AT, via :after-push). The three commands now build a spec and call it, and the comment's own-comment permission check stays in its caller. No behavior change — 353 tests green, including the sync, title-sync, comment-editing, and conflict suites.
* refactor: extract the issue-at-point guard into a helperCraig Jennings2026-05-241-20/+18
| | | | The guard `(unless (ignore-errors (org-back-to-heading t) t) (user-error ...))` was copy-pasted across ten issue-at-point commands. I pulled it into pearl--goto-heading-or-error, which takes an optional message so the one comment-specific variant ("Not on a Linear comment") still reads right. Same point move, same user-error type and text — no behavior change; 353 tests green.
* refactor: drop the unused pearl--cache-issues variableCraig Jennings2026-05-241-5/+2
| | | | The variable was only ever declared and set to nil in pearl-clear-cache — never populated or read anywhere. I removed the defvar, the clear-cache line, and the stale "issues" mention in that docstring. No behavior change; the suite stays at 353 green.
* docs: title the README with the PEARL backronym and add an epigraphCraig Jennings2026-05-241-2/+5
| | | | I made the recursive backronym the README title — Pearl Edits and Reflects Linear (P=Pearl, E=Edits, A=and, R=Reflects, L=Linear) — and named it in the opening line, matching the sibling projects' READMEs (Chime, emacs-wttrin). Added an Umberto Eco epigraph, "We like lists because we don't want to die.", in the same quote style they use. The cgit repo description on cjennings.net is set to match.
* docs: restructure the README and repoint URLs to cjennings.netCraig Jennings2026-05-244-238/+274
| | | | I rewrote the README into a sectioned guide — a nav header over Features, Installation, Quick Start, Commands, The Org File, Configuration, Development & Testing, FAQ, Troubleshooting, History, and License — and repointed every repository URL off the deleted github.com/cjennings/pearl onto https://git.cjennings.net/pearl.git. The repoint also covers the URL header in pearl.el, the Eask website-url, and the package-summary repo link. The upstream credit to Gael Blanchemain's linear-emacs is unchanged.
* feat: pearl — manage Linear issues from org-modeCraig Jennings2026-05-2458-0/+11447
Pearl fetches Linear issues into an org file and syncs edits back. It covers list / custom views / saved queries, per-issue and bulk rendering with comments inline, conflict-aware sync of descriptions, titles, and comments, field commands for priority / state / assignee / labels, and a transient dispatch menu. The render folds to a scannable outline and nests issues under a sortable parent. Based on and inspired by Gael Blanchemain's linear-emacs.