diff options
| -rw-r--r-- | README.org | 49 | ||||
| -rw-r--r-- | docs/saved-query-sync-spec.org | 2 |
2 files changed, 45 insertions, 6 deletions
@@ -188,17 +188,53 @@ With =which-key=, each step shows a labeled menu. Every command is also availabl A *source* is anything Pearl can fetch from: a Linear favorite, a Custom View, a project / cycle / label / user, a local saved query, or an ad-hoc filter. The everyday front door is =pearl-pick-source= (=C-; L f s=, or =P= in the transient), which lists all your Linear favorites first (in their Linear sort order) and then your local saved queries, each tagged by kind: #+begin_example - [view] Active sprint bugs - [project] Orchestration Dashboard - [user] Vrezh Mikayelyan - [label] security - [saved] My open work + [view] Active sprint bugs + [project] Orchestration Dashboard + [user] Vrezh Mikayelyan + [label] security + [saved] My open work + [saved → Eng] Active sprint bugs (synced) + [saved → Personal] My ICEBOX scratchpad (synced) #+end_example Pick one and it fetches. List-capable favorites (Custom View / project / cycle / label / user) resolve to the existing filter or view fetch and render into the active file; non-list favorites (issue, document, dashboard, ...) open in the browser instead. Favorites are picker entries, never persisted -- a chosen favorite resolves to a concrete filter/view source before rendering, so refresh re-runs that resolved source and stays stable even if your favorites list later changes. Label and user favorites resolve by id, not by name or email, so renames in Linear don't break a saved fetch. +A saved query you've synced up to Linear (see /Publishing a saved query as a Linear view/ below) renders as =[saved → <Team or Personal>] Name= so you can see at a glance which scope it lives in. The arrow reads "where it lives now." Picking it dispatches as a Linear view (server-side filter run via =customView(id:)=), not as a local filter -- any drift you've made to the view on Linear's side is honored. Plain =[saved]= entries are local-only and still run the authoring filter Pearl has on disk. =[saved → ?]= means the entry is synced but Pearl couldn't resolve the team id (deleted or renamed on Linear) -- the dispatch still works, the label is just flagging stale scope metadata. + If the favorites fetch fails (network/auth/transport), the picker still offers any local saved queries -- a failed fetch is not the same as "no favorites." With neither favorites nor saved queries, =pearl-pick-source= refuses with a clear message naming the missing setup. +*** Publishing a saved query as a Linear view + +A local saved query is great for your own quick filters, but the team can't see it and the Linear web UI can't run it. =pearl-sync-saved-query-to-linear= (=C-; L f S=, or =S= in the transient's Fetch group) promotes a local saved query to a Linear Custom View so it's visible alongside your sidebar favorites and the rest of the team's views. + +Two commands publish: + +| Command | What it does | +|------------------------------------+--------------------------------------------------------------------| +| =pearl-sync-saved-query-to-linear= | Pick a saved query by name and publish (or update) it on Linear | +| =pearl-publish-current-source= | Read the buffer's =#+LINEAR-SOURCE=; if it names a local saved query, publish that one | + +The first time you sync a query, Pearl asks where the view should live with one enriched prompt that spells out the full end-state per option: + +#+begin_example + Where does this view live? + [ Team: Engineering, visible to the team ] + [ Personal, only I see it ] + [ Team: Engineering, only I see it ] + [ Team: Marketing, visible to the team ] + [ Team: Marketing, only I see it ] + ... + [ Cancel. ] +#+end_example + +The default is the team in your filter's =:team= key (if any) shared to that team, else =[ Personal, only I see it ]=. If a view with the same name already exists in the chosen scope, Pearl prompts =Replace? Rename? Cancel?= -- Replace updates the existing view's filter by id (preserving its url and anyone's favorites on it), Rename re-prompts for a different name, Cancel aborts cleanly. + +After a successful sync, the saved-query entry gains four metadata keys (=:linear-view-id=, =:linear-view-team-id=, =:linear-view-shared=, =:linear-view-synced-at=) plus =:linear-view-url= so =pearl-open-current-view-in-linear= can dispatch to the browser. Re-syncing the same query calls =customViewUpdate= against the stored id -- the view is overwritten with whatever your local filter says now. This is one-way push by design: the verb is "publish my version." If you edit the view on Linear's side and then re-sync from Pearl, your local plist wins. + +Pearl's =:sort= / =:order= on a saved query don't sync up in v1 -- Linear's =CustomView= API has no sort input, so a synced view renders in whatever order Linear's defaults give it. If sort order is load-bearing for the view, set it in the Linear web UI after the first sync. + +=pearl-delete-saved-query= (=C-; L k q=) on a synced entry asks a second question after the local-delete confirmation: also delete the linked Linear view? Yes calls =customViewDelete=, which Linear handles as a soft delete (recoverable from the workspace trash). No unlinks the entry locally and leaves the Linear view in place. If the Linear delete fails (permissions, network), Pearl prompts to drop the local entry anyway -- accepting orphans the Linear view, and the message names the view id explicitly so you can clean it up by hand. + *** Fetching and refreshing | Command | What it does | @@ -209,6 +245,9 @@ If the favorites fetch fails (network/auth/transport), the picker still offers a | =pearl-list-issues-filtered= | Build an ad-hoc issue filter interactively | | =pearl-run-view= | Run a Linear Custom View server-side | | =pearl-run-saved-query= | Run a named local query from =pearl-saved-queries= | +| =pearl-sync-saved-query-to-linear= | Publish (or update) a saved query as a Linear Custom View | +| =pearl-publish-current-source= | Publish the current buffer's saved query (reads =#+LINEAR-SOURCE=) | +| =pearl-delete-saved-query= | Delete a saved query (and optionally its linked Linear view) | | =pearl-refresh-current-view= | Re-run the source recorded in the active file | | =pearl-refresh-current-issue= | Re-fetch the issue at point | diff --git a/docs/saved-query-sync-spec.org b/docs/saved-query-sync-spec.org index 887447c..a17239e 100644 --- a/docs/saved-query-sync-spec.org +++ b/docs/saved-query-sync-spec.org @@ -4,7 +4,7 @@ * Status -*Ready.* Sprint review with Craig completed 2026-05-28; all six open questions dispositioned (Q1, Q2, Q3, Q4, Q5, Q6, plus probe-surfaced Q7 absorbed by the Q2 collapse). Triggered by the conversation on 2026-05-28 after =pearl-delete-saved-query= shipped ("we'll need functionality that allows us to delete these, and also syncs non-local versions back to Linear"). Sibling spec to =docs/issue-sources-spec.org=, which established favorites + the unified picker but kept local saved queries as a local-only Customize variable. +*Done.* Shipped 2026-05-28 across commits 60a026b (Phase 2: pearl-sync-saved-query-to-linear + transient entry), c8b9ad1 (Phase 3: extend pearl-delete-saved-query with delete-on-Linear prompt), fd94237 (Phase 4: pearl-pick-source distinguishes [saved] / [saved → scope] / [view] + :linear-view-url plumbing), and 0975e7d (Phase 5: pearl-publish-current-source convenience wrapper). README sync-up section and this status flip in the Phase 6 docs commit. Sprint review with Craig completed 2026-05-28; all six open questions dispositioned (Q1, Q2, Q3, Q4, Q5, Q6, plus probe-surfaced Q7 absorbed by the Q2 collapse). Triggered by the conversation on 2026-05-28 after =pearl-delete-saved-query= shipped ("we'll need functionality that allows us to delete these, and also syncs non-local versions back to Linear"). Sibling spec to =docs/issue-sources-spec.org=, which established favorites + the unified picker but kept local saved queries as a local-only Customize variable. Live-API probe completed 2026-05-28 (see § Review dispositions). Mutations confirmed: =customViewCreate=, =customViewUpdate=, =customViewDelete= (no archive). =CustomViewCreateInput.filterData= is type =IssueFilter= -- the same type pearl's existing compiler produces, so the mapping is a pass-through. Implementation phase 1 trivializes accordingly. |
