aboutsummaryrefslogtreecommitdiff
path: root/README.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-23 22:36:55 -0400
committerCraig Jennings <c@cjennings.net>2026-06-23 22:36:55 -0400
commit92ae9d8b56a9fabadf6ea8beb2163655c7a8ffb8 (patch)
tree104aa85fc8ab8e799b7066792019848263b5f3ae /README.org
parent8061ec3d83fdd9896f84e4d8590eb878277e4949 (diff)
downloadpearl-92ae9d8b56a9fabadf6ea8beb2163655c7a8ffb8.tar.gz
pearl-92ae9d8b56a9fabadf6ea8beb2163655c7a8ffb8.zip
docs(readme): document themeable faces and reflow tables
I added a "Faces and theming" subsection under Configuration: a table of the six pearl faces (where each shows, what it inherits), plus a note that the rest of the rendered surface inherits standard Org faces and isn't separately themeable yet. I also reflowed the existing tables to a consistent width with per-row rules. Content is unchanged. The pass corrected two stale alignment defects in the Configuration table.
Diffstat (limited to 'README.org')
-rw-r--r--README.org252
1 files changed, 182 insertions, 70 deletions
diff --git a/README.org b/README.org
index e553321..72aefd5 100644
--- a/README.org
+++ b/README.org
@@ -161,23 +161,39 @@ Pearl is fully keyboard-drivable. =pearl-mode=, a minor mode, turns on automatic
The hot-path commands sit one key under the prefix; the rest are grouped into sub-maps, one per action verb or noun. Fetch is issue sources only; views, account switching, and setup each have their own group. The common ones appear in both places, so =d= and =e d= both edit the description.
-| Key | Command |
-|-----------+----------------------------------------------------------------------|
-| =l= | open the default view (my open issues until you set one) |
-| =g= | refresh the view |
-| =r= | refresh the issue at point |
-| =s= / =S= | save the issue at point / save every issue in the file |
-| =d= | edit the description |
-| =@= | mention a Linear user (=@displayName=) at point |
-| =m= | open the full transient menu |
-| =f= ... | fetch (issue sources): =s= pick source, =o= open issues, =p= by project, =f= filter |
-| =v= ... | views: =l= run local, =L= run Linear, =c= create, =e= edit, =k= delete, =u= publish, =U= publish current, =d= save Linear view locally, =D= set default, =s= sort, =g= group, =r= reverse order |
-| =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment |
-| =c= ... | create: =t= issue, =c= comment |
-| =k= ... | delete: =t= issue, =c= comment |
-| =o= ... | open: =i= issue in browser, =v= view in Linear |
-| =y= ... | copy: =u= issue URL |
-| =w= ... | workspace: =a= switch account, =t= test connection, =c= check setup, =!= toggle debug, =x= clear cache |
+| Key | Command |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =l= | open the default view (my open issues until you set one) |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =g= | refresh the view |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =r= | refresh the issue at point |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =s= / =S= | save the issue at point / save every issue in the file |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =d= | edit the description |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =@= | mention a Linear user (=@displayName=) at point |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =m= | open the full transient menu |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =f= ... | fetch (issue sources): =s= pick source, =o= open issues, =p= by project, =f= filter |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =v= ... | views: =l= run local, =L= run Linear, =c= create, =e= edit, =k= delete, =u= publish, =U= publish |
+| | current, =d= save Linear view locally, =D= set default, =s= sort, =g= group, =r= reverse order |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =c= ... | create: =t= issue, =c= comment |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =k= ... | delete: =t= issue, =c= comment |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =o= ... | open: =i= issue in browser, =v= view in Linear |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =y= ... | copy: =u= issue URL |
+|-----------+----------------------------------------------------------------------------------------------------------|
+| =w= ... | workspace: =a= switch account, =t= test connection, =c= check setup, =!= toggle debug, =x= clear cache |
+|-----------+----------------------------------------------------------------------------------------------------------|
To reach the map outside a Pearl buffer, bind it globally as well:
@@ -221,10 +237,14 @@ A local view is great for your own quick filters, but the team can't see it and
Two commands publish:
-| Command | Binding | What it does |
-|------------------------------------+--------------------------------------+--------------------------------------------------------------------|
-| =pearl-publish-local-view= | =C-; L v u=, =u= in the transient Views group | Pick a local view by name and publish (or update) it on Linear |
-| =pearl-publish-current-view= | =C-; L v U=, =U= in the transient Views group | Read the buffer's =#+LINEAR-SOURCE=; if it names a local view, publish that one |
+| Command | Binding | What it does |
+|------------------------------+-------------------------------------------+-------------------------------------------|
+| =pearl-publish-local-view= | =C-; L v u=, =u= in the transient Views | Pick a local view by name and publish (or |
+| | group | update) it on Linear |
+|------------------------------+-------------------------------------------+-------------------------------------------|
+| =pearl-publish-current-view= | =C-; L v U=, =U= in the transient Views | Read the buffer's =#+LINEAR-SOURCE=; if |
+| | group | it names a local view, publish that one |
+|------------------------------+-------------------------------------------+-------------------------------------------|
The first time you publish a local view, Pearl asks where the view should live with one enriched prompt that spells out the full end-state per option:
@@ -255,29 +275,44 @@ Linear stores a view's filter as an =and=/=or= tree that's richer than Pearl's A
*** Creating and editing local views
-| Command | Binding | What it does |
-|------------------------------+----------------+------------------------------------------------------|
-| =pearl-create-local-view= | =C-; L v c= | Build a filter interactively and save it as a local view |
-| =pearl-edit-local-view= | =C-; L v e= | Edit a local view's filter, name, sort, and order, preserving its metadata |
-| =pearl-save-linear-view-locally= | =C-; L v d= | Copy a favorited Linear view down into a new editable local view |
+| Command | Binding | What it does |
+|----------------------------------+-------------+---------------------------------------------------------------------|
+| =pearl-create-local-view= | =C-; L v c= | Build a filter interactively and save it as a local view |
+|----------------------------------+-------------+---------------------------------------------------------------------|
+| =pearl-edit-local-view= | =C-; L v e= | Edit a local view's filter, name, sort, and order, preserving its |
+| | | metadata |
+|----------------------------------+-------------+---------------------------------------------------------------------|
+| =pearl-save-linear-view-locally= | =C-; L v d= | Copy a favorited Linear view down into a new editable local view |
+|----------------------------------+-------------+---------------------------------------------------------------------|
=pearl-list-issues-filtered= also offers to save its ad-hoc filter as a local view at the end. Create, edit, and copy-down all prompt Replace / Rename / Cancel on a name collision, and under multiple accounts stamp the active account on a new view and refuse a cross-account edit.
*** Fetching and refreshing
-| Command | What it does |
-|------------------------------+--------------------------------------------------|
-| =pearl-pick-source= | Pick a Linear favorite or local view and fetch it |
-| =pearl-list-issues= | Fetch your open issues |
-| =pearl-list-issues-by-project= | Fetch every open issue in a chosen project (all assignees) |
-| =pearl-list-issues-filtered= | Build an ad-hoc issue filter interactively |
-| =pearl-run-linear-view= | Run a Linear Custom View server-side |
-| =pearl-run-local-view= | Run a named local view from =pearl-local-views= |
-| =pearl-publish-local-view= | Publish (or update) a local view as a Linear Custom View |
-| =pearl-publish-current-view= | Publish the current buffer's local view (reads =#+LINEAR-SOURCE=) |
-| =pearl-delete-local-view= | Delete a local view (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 |
+| Command | What it does |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-pick-source= | Pick a Linear favorite or local view and fetch it |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-list-issues= | Fetch your open issues |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-list-issues-by-project= | Fetch every open issue in a chosen project (all assignees) |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-list-issues-filtered= | Build an ad-hoc issue filter interactively |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-run-linear-view= | Run a Linear Custom View server-side |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-run-local-view= | Run a named local view from =pearl-local-views= |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-publish-local-view= | Publish (or update) a local view as a Linear Custom View |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-publish-current-view= | Publish the current buffer's local view (reads =#+LINEAR-SOURCE=) |
+|--------------------------------+-------------------------------------------------------------------|
+| =pearl-delete-local-view= | Delete a local view (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 |
+|--------------------------------+-------------------------------------------------------------------|
Running a Linear Custom View (via =pearl-run-linear-view= or by picking a favorited view in =pearl-pick-source=) mirrors the view as Linear shows it: its own filter runs server-side, its configured sort order is reproduced, and its "completed issues" display setting is honored. A view set to hide completed issues shows only open issues in Pearl too; a view set to show completed issues from the past day/week/month/quarter/year shows open issues plus those completed or canceled within that window (the windows are fixed-day approximations of Linear's). Refreshing the view recomputes the window relative to the refresh time.
@@ -311,21 +346,34 @@ Like sorting, the regroup moves whole subtrees so unsaved edits survive. Only th
Pearl has one write path. Edit an issue however you like in the buffer, then save it -- Pearl diffs each field against what it last fetched and pushes only what changed. Nothing pushes the moment you pick a value, and there is no per-field "push" command to remember. All issue commands work from anywhere inside an issue subtree.
-| Command | What it does |
-|-----------------------------------+-------------------------------------------------------|
-| =pearl-save-issue= | Save every changed field of the issue at point |
+| Command | What it does |
+|-------------------------------------+------------------------------------------------------|
+| =pearl-save-issue= | Save every changed field of the issue at point |
+|-------------------------------------+------------------------------------------------------|
| =pearl-save-all= | Save every changed issue in the file (confirms once) |
+|-------------------------------------+------------------------------------------------------|
| =pearl-edit-description= | Edit the description in a focused compose buffer |
+|-------------------------------------+------------------------------------------------------|
| =pearl-edit-state= | Pick a workflow state (reaches any of the team's) |
+|-------------------------------------+------------------------------------------------------|
| =pearl-edit-assignee= | Pick a team member as assignee |
+|-------------------------------------+------------------------------------------------------|
| =pearl-edit-labels= | Pick labels; an empty selection clears them |
-| =pearl-create-comment= | Add a new Linear comment |
+|-------------------------------------+------------------------------------------------------|
+| =pearl-create-comment= | Add a new Linear comment |
+|-------------------------------------+------------------------------------------------------|
| =pearl-edit-current-comment= | Edit one of your own comments |
+|-------------------------------------+------------------------------------------------------|
| =pearl-delete-current-comment= | Delete one of your own comments after confirming |
+|-------------------------------------+------------------------------------------------------|
| =pearl-fetch-all-comments= | Load the issue's full comment thread (=C-; L f c=) |
+|-------------------------------------+------------------------------------------------------|
| =pearl-delete-current-issue= | Soft-delete the current issue after confirmation |
+|-------------------------------------+------------------------------------------------------|
| =pearl-open-current-issue= | Open the issue URL in a browser |
+|-------------------------------------+------------------------------------------------------|
| =pearl-open-current-view-in-linear= | Open the active view in Linear |
+|-------------------------------------+------------------------------------------------------|
How each field is edited:
@@ -410,27 +458,46 @@ If you prefer a tidier outline, =pearl-title-case-headings= opts issue titles in
Most users only need an API key and an output path. The rest are knobs for teams with large issue sets or stronger preferences about window behavior.
-| Variable | Purpose |
-|-----------------------------+---------------------------------------------------|
+| Variable | Purpose |
+|-------------------------------+---------------------------------------------------|
| =pearl-api-key= | Linear API key (single-account) |
+|-------------------------------+---------------------------------------------------|
| =pearl-org-file-path= | Active Org output file |
+|-------------------------------+---------------------------------------------------|
| =pearl-default-team-id= | Default team for issue creation |
+|-------------------------------+---------------------------------------------------|
| =pearl-accounts= | Named accounts for multiple workspaces |
+|-------------------------------+---------------------------------------------------|
| =pearl-default-account= | Account made active at first need |
-| =pearl-local-views= | Named local views |
+|-------------------------------+---------------------------------------------------|
+| =pearl-local-views= | Named local views |
+|-------------------------------+---------------------------------------------------|
| =pearl-max-issue-pages= | Pagination cap, 100 issues per page |
+|-------------------------------+---------------------------------------------------|
| =pearl-request-timeout= | Synchronous request timeout in seconds |
+|-------------------------------+---------------------------------------------------|
| =pearl-fold-after-update= | Re-fold the active page after fetch/refresh |
+|-------------------------------+---------------------------------------------------|
| =pearl-title-case-headings= | Opt-in smart title case for issue titles (off) |
+|-------------------------------+---------------------------------------------------|
| =pearl-show-glyphs= | Master switch for heading glyphs (on) |
+|-------------------------------+---------------------------------------------------|
| =pearl-ticket-glyph= | Glyph on issue headings (🎫) |
-| =pearl-comment-glyph= | Glyph on each comment heading (🗨️) |
+|-------------------------------+---------------------------------------------------|
+| =pearl-comment-glyph= | Glyph on each comment heading (🗨️) |
+|-------------------------------+---------------------------------------------------|
| =pearl-comments-header-glyph= | Glyph on the Comments container heading (💬) |
+|-------------------------------+---------------------------------------------------|
| =pearl-surface-buffer= | Show the active buffer after a command updates it |
+|-------------------------------+---------------------------------------------------|
| =pearl-surface-select-window= | Move focus to the surfaced buffer |
+|-------------------------------+---------------------------------------------------|
| =pearl-compose-window-side= | Side the compose/conflict buffer opens from |
+|-------------------------------+---------------------------------------------------|
| =pearl-compose-window-size= | Size of that side window (fraction or lines) |
-| =pearl-debug= | Log request/response details to =*Messages*= |
+|-------------------------------+---------------------------------------------------|
+| =pearl-debug= | Log request/response details to =*Messages*= |
+|-------------------------------+---------------------------------------------------|
The team default is optional. Leave =pearl-default-team-id= unset and Pearl asks which team only when you run =pearl-create-issue= or =pearl-list-issues-by-project=. Nothing else needs it. Set it to skip that prompt.
@@ -442,15 +509,45 @@ Pearl hands every URL it opens (issue links, view links, browser-only favorites)
Common shapes:
-| Value | Behavior |
-|---------------------------------------------+----------------------------------------------------------------|
-| ='browse-url-default-browser= | Emacs autodetects (usually correct) |
-| ='browse-url-xdg-open= | Honor the desktop default (=xdg-mime= for http/https) |
-| ='browse-url-firefox= / =-chrome= / =-chromium= | Always that browser |
-| ='browse-url-generic= + =browse-url-generic-program= | Any command you name |
+| Value | Behavior |
+|------------------------------------------------------+-------------------------------------------------------|
+| ='browse-url-default-browser= | Emacs autodetects (usually correct) |
+|------------------------------------------------------+-------------------------------------------------------|
+| ='browse-url-xdg-open= | Honor the desktop default (=xdg-mime= for http/https) |
+|------------------------------------------------------+-------------------------------------------------------|
+| ='browse-url-firefox= / =-chrome= / =-chromium= | Always that browser |
+|------------------------------------------------------+-------------------------------------------------------|
+| ='browse-url-generic= + =browse-url-generic-program= | Any command you name |
+|------------------------------------------------------+-------------------------------------------------------|
For per-URL routing (e.g. "Linear goes to the work-account browser, everything else to my personal one"), set =browse-url-handlers= with a list of =(REGEXP . FUNCTION)= pairs.
+*** Faces and theming
+
+Pearl defines six faces, all in the =pearl= customize group (=M-x customize-group RET pearl=). Each inherits a sensible default from a standard or =diff-mode= face, so they look right out of the box and you only touch them if you want to. Override any of them via Customize or your theme.
+
+| Face | Where it shows | Default |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-preamble-summary= | The one-line "Pearl - …" summary that | inherits =org-document-title=, goldenrod |
+| | hides the =#+= header block | foreground |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-modified-highlight= | An issue field with unsaved, pushable | inherits =diff-changed= |
+| | local edits | |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-modified-local= | A locally-edited comment that can't be | inherits =secondary-selection= |
+| | pushed (someone else's / a bot's) | |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-modified-unknown= | A changed comment whose ownership isn't | inherits =pearl-modified-local= |
+| | resolved yet | |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-editable-comment= | Comment headings you can edit | inherits =success= (green) |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+| =pearl-readonly-comment= | Comments you can't edit, dimmed to read as | grey, darker than =shadow= |
+| | disabled | |
+|----------------------------+--------------------------------------------+--------------------------------------------|
+
+Everything else Pearl renders — issue headings, the =LINEAR-*= drawers, priority cookies, assignee =@=-tags, label tags, the help header, group section headings — uses standard Org faces (=org-level-N=, =org-todo=, =org-priority=, =org-tag=, =org-drawer=). Theme those through your Org theme; Pearl adds no separate hook for them.
+
** Multiple accounts
:PROPERTIES:
:CUSTOM_ID: multiple-accounts
@@ -470,11 +567,14 @@ If you work more than one Linear workspace — say a work account and a personal
=:api-key-source= says how the key is *found*, not the key itself. Three forms:
-| Form | Where the key comes from |
-|-----------------------------------------+---------------------------------------------------|
-| =(:auth-source :host H :user U)= | =~/.authinfo.gpg= (the documented default) |
-| =(:env "VAR")= | the named environment variable |
-| =(:literal "lin_...")= | the string inline (an escape hatch) |
+| Form | Where the key comes from |
+|----------------------------------+--------------------------------------------|
+| =(:auth-source :host H :user U)= | =~/.authinfo.gpg= (the documented default) |
+|----------------------------------+--------------------------------------------|
+| =(:env "VAR")= | the named environment variable |
+|----------------------------------+--------------------------------------------|
+| =(:literal "lin_...")= | the string inline (an escape hatch) |
+|----------------------------------+--------------------------------------------|
The auth-source form keeps the key out of your config. An =~/.authinfo.gpg= line for the example above:
@@ -518,20 +618,32 @@ Clone the repo and install the Eask-managed dependencies:
Useful development targets:
-| Target | What it does |
-|----------------------------------------+-------------------------------------------------------|
+| Target | What it does |
+|------------------------------------------+---------------------------------------------------------|
| =make test= | Run unit and integration tests, excluding =:slow= tests |
+|------------------------------------------+---------------------------------------------------------|
| =make test-all= | Run every test, including =:slow= tests |
-| =make test-unit= | Run unit tests only |
-| =make test-integration= | Run integration tests only |
-| =make test-file FILE=mapping= | Run one test file by fuzzy match |
-| =make test-one TEST=priority= | Run one test by fuzzy match |
-| =make test-name TEST='test-pearl-map-*'= | Run tests matching an ERT selector |
-| =make coverage= | Generate undercover/simplecov coverage data |
+|------------------------------------------+---------------------------------------------------------|
+| =make test-unit= | Run unit tests only |
+|------------------------------------------+---------------------------------------------------------|
+| =make test-integration= | Run integration tests only |
+|------------------------------------------+---------------------------------------------------------|
+| =make test-file FILE=mapping= | Run one test file by fuzzy match |
+|------------------------------------------+---------------------------------------------------------|
+| =make test-one TEST=priority= | Run one test by fuzzy match |
+|------------------------------------------+---------------------------------------------------------|
+| =make test-name TEST='test-pearl-map-*'= | Run tests matching an ERT selector |
+|------------------------------------------+---------------------------------------------------------|
+| =make coverage= | Generate undercover/simplecov coverage data |
+|------------------------------------------+---------------------------------------------------------|
| =make compile= | Byte-compile =pearl.el= |
-| =make lint= | Run the lint/checkdoc path from the test harness |
-| =make validate= | Check parentheses across source and tests |
-| =make clean= | Remove test artifacts and coverage output |
+|------------------------------------------+---------------------------------------------------------|
+| =make lint= | Run the lint/checkdoc path from the test harness |
+|------------------------------------------+---------------------------------------------------------|
+| =make validate= | Check parentheses across source and tests |
+|------------------------------------------+---------------------------------------------------------|
+| =make clean= | Remove test artifacts and coverage output |
+|------------------------------------------+---------------------------------------------------------|
Each test file runs in its own Emacs batch process for isolation. See [[file:TESTING.org][TESTING.org]] for the full test guide, naming conventions, fixture helpers, and coverage notes.