Bob Refs
Bob Refs is a thin client of bob: it filters, ranks, sections, and presents what bob returns, never parses frontmatter or note bodies, and never writes the vault itself. Keyboard rows also appear in Keyboard. The inspector column is in Bob Refs inspector. Ranking invocation is in Diagnostics. Disk, memory, and Accessibility rules are in Permissions. Scan, load, and tab-close recovery are in Recover Bob Refs.
Bob Refs¶
Synthetic CI render. Open the image for full-size text.
Bob Refs is a quick-open panel for the reference library: one keystroke shows
every PDF-backed reference note, with kind and reading state on every row.
Return opens the original PDF in Highlights and changes nothing in the vault —
opening never mutates reading state, task lanes, or review state.
Bob Refs ⌘S runs bob ref scan -a -w and presents its JSON report.
When enabled, Bob Mac Capture passively observes ⌘S in Highlights and scans
after the library PDF save settles. The 15-minute cron runs without -a.
An older bob that rejects -a is retried without it and remembered
for that executable.
Data¶
- Snapshot:
bob ref list -R all -A -f json(schema version 1), plus-gwhen some row still lacks anaddeddate, or has a git date but no git instant yet. Every optional field decodes withdecodeIfPresent, so an olderbobstill works: noblockedreads as not blocked, a row withouttaskjoins Today on the note path, and unknown fields are ignored. Any other schema version is rejected like any other refresh failure.added_atis the most precise instantbobknows on the row'saddedday (RFC 3339, for example2026-10-17T09:12:44-04:00) and isnullwhenbobknows only the day. It parses once intoRefItem.addedAtwhen the catalog is built; an olderbobdecodes it as nil, and a malformed value becomes nil without failing the row. Each row'staskobject (path,block_id,link,mark,archived;block_idrefon frozen v1 trackers) names the reference's single ordinary reading task: where it lives is the reference's parent, and adone/path reads as archived. - Today:
bob plan -f json(schema version 2). Today entries join on(task.path, task.block_id), so two references whose tasks share one parent note no longer both light up when only one is linked; rows without a task fall back to the note-path join. - Scan:
bob ref scan -a -w -f json(schema version 1) names every created and updated note, the intake moves, held PDFs, and per-PDF failures or a coded hard failure. Additive fields (generated_at,summary.held,held[],notes[].marker_write) decode as empty on an olderbob. The app decodes every field exceptschema_versionloosely, ignores unknown fields, and rejects any other schema version like any other scan failure. After a writing scan,bobrewrites<state>/bob-cli/ref/last-scan.json(XDG_STATE_HOME, else~/.local/state) and, on macOS, posts the Darwin notificationorg.bobs.bob.ref.scan-finished. Bob Refs observes that ping, reads the receipt off the main thread (bounded at 4 MB), and ignores a duplicate content hash. Launch never reads a receipt. - The snapshot (with git-added dates and instants) and the last Today
value persist
in
refs-snapshot.jsonunder~/Library/Application Support/org.bobs.bob-mac-capture/, so a cold launch paints immediately.git_added_atmaps each git-dated note path to its birth commit's RFC 3339 instant; older caches decode it as empty and the-gpass refills it once. Open history (note paths and timestamps only) lives inrefs-open-log.jsonbeside it; NEW marks (new/seenplus the instant) inrefs-new-marks.json. All three files are mode 0600 in a 0700 directory. RefsCore(theSources/RefsCoretarget, Foundation plus CaptureCore only) owns the decoding, theRefItem/RefKind/RefState/RefScopemodel, the ranking, captions, and inspector extraction, theBobRefsFetcheroverBobProcessClient, and the stores.
Updates¶
Bob Refs remembers its query, kind scope, Newest and Tabs filters, selected
reference, revealed highlight pages, and list and preview positions in memory
for the app's lifetime. Every ordinary show restores that view using fresh
library or Tabs data, except when a reference was added since the panel was
last shown and its added day is within the 3-day NEW window (renames and
moves of older notes do not count): that show resets to the fresh-launch
defaults and selects the new reference (the newest batch, then the first
listed id in that batch). An arrival that lands in the first 10 seconds of
an untouched ordinary show — typically the show's own pull — also resets
and selects it. A Ctrl+Tab switch never resets for an arrival: the new
reference joins the listing without moving the selection. The Highlights Tabs
key (Command-T) always opens from fresh-launch defaults, as Ctrl-C resets,
with Tabs on, and still defers a new-reference reset to the next ordinary
show.
Selecting the reference first cancels the reset. Ctrl-C resets it to the
fresh-launch defaults; quitting or restarting starts fresh.
Visible inspectors rehydrate notes, highlights, thumbnail, and outline after a library refresh, keeping selection, scroll position, and revealed page while old content stays until the new load completes.
RefsLibrary owns the refresh service: the snapshot, the -g
backfill on its own lane, Today, a one-shot Spotlight sweep for
last-used dates and page counts, and missing-PDF checks. It paints from
cache at launch and watches the vault root (root *.md notes) plus
<vault>/ref, <vault>/lib, and <vault>/done
(0.5 s debounce). Dot-path-only batches (.git/, .obsidian/, .trash/) no
longer trigger a refresh; any visible path, including a lib/ PDF, still
does. Today refreshes every 10 minutes, after sleep, after
every capture, and in the background on panel open unless a panel-open
Today refresh ran in the previous five seconds; a stale
snapshot is re-read on every panel open. The -g lane runs only while
some row lacks an added date (or a git-dated row lacks its
instant) and only after the snapshot
publishes, so the first paint never waits; it merges
git-sourced dates into dateless rows, and a -g failure keeps the
published list, only logged. One refresh per lane runs at a time;
a trigger during a refresh schedules exactly one follow-up. A failed
refresh keeps the last good list and records .failed, which the
footer reads as "Update failed · ⌘R to retry". Only Highlights and
default-app opens append to the open log. RefsPanelModel holds the
query, scope, frozen listing, and id-based selection: typed text reaches
the model on every keystroke, late library data refreshes row content
in place; arrivals that land while the panel is open join the visible
list on their own when the Tabs filter is off, no rename, Today-link,
or copy card is open or in flight, there is no pending open, and the
selected id is nil or still available (otherwise they join at the next
re-rank, with a "1 new reference" / "N new references" VoiceOver
announcement when they join; ⌘R re-ranks from the new data once the
refresh completes, keeping the selection when it still exists), an
open error re-shows the panel with the query, selection,
and pending open intact, and Return on a vanished row shows "No longer
in your library" instead of opening whatever slid into its index. A refs-pull
lane runs bob vault-sync -q on every Refs panel show (throttled to one
pull per 5 s) and before every ⌘R refresh (single-flight, unthrottled);
a trigger that arrives while a pull is running joins that pull, no pull
runs during a scan or rename, and a pull failure is only logged — it never
touches refreshState, banners, or the footer. The pull itself never
refreshes; the existing vault watcher sees the fast-forward and runs the
normal snapshot refresh. An ⌘S scan runs on refs-scan (300 s timeout)
without cancelling other lanes. Watcher changes defer one refresh until
it completes; a save during a scan or rename queues one follow-up. After
bob exits, each completion refreshes the snapshot and Today, even on
failure, before publishing the scan notice, so moved intake files appear
before re-ranking.
Scan after saving in Highlights¶
With Scan after ⌘S in Highlights enabled, Bob Mac Capture passively
observes plain Command-S while Highlights (or the override app) is frontmost;
the key still reaches Highlights unchanged. Only lib//xlib/ documents
scan: an unknown path permits the full scan, an outside path disarms it. The
trigger waits for quiet library events for 1 second, falls back after 2.5
seconds without events, and caps at 10 seconds.
The scan runs bob ref scan -a -w -f json. With
highlights.coordinate_pdf_writes: true, bob's coordinated .forMerging
write saves first, applies the marker to fresh bytes, and reloads the same tab.
With coordination off, Highlights may show “changed by another application”;
choose Save Anyway to keep annotations. About 350 PDFs take 8 seconds.
No save-only notification is added; scan and open-PDF receipt rules stay.
Sorting¶
An empty query shows browse sections in order — Just scanned (this scan's
created references, in bob's order, uncapped, while the scan mark is at
most 15 minutes old), NEW (Ready/Next rows that are marked new, or unmarked
unopened arrivals within 3 local days, newest first by new-since,
uncapped), Now (references whose reading task has a Task Link under today's open
Pomodoros, in ledger order),
Reading, Next (blocked rows last), Ready by added desc (blocked rows
last), Recently opened (read/dropped/unknown opened within 14 days,
capped at 5), then the Library by last activity. The Now section header
reads NOW, distinct from Newest's TODAY date group. NEW is Mac-local
presentation state: it never runs bob, never writes the vault, and is
not the review walk's NEW freshness tier. A marked row's new-since is
the mark instant; an arrived row uses addedAt when known, else the
start of its added day. Day counts use the
panel calendar, so NEW, Recently opened, recency, and Library
activity never shift by a day in US evenings. Each item appears in the
first section that admits it; over-cap rows fall through; empty sections
are omitted; the final tie-break everywhere is the note path. Tabs mode
orders the current tab first, then recent tabs, with tab-bar order as the
tie-break and unmatched tabs last. A typed
query collapses the sections into one ranked list with tiers that never
cross: T0 exact (title/stem equality or a pasted arXiv id, DOI, or URL),
T1 title/stem word prefix (any length; a 2-character prefix counts),
T2 fuzzy (q >= 0.55; two characters need a contiguous non-word
substring, one character needs a word prefix), T3 author/area/kind/outline
heading. Within a tier, score = m + P + W + L + F + R: mean token
quality, prefix bonus 0.15, whole-word bonus 0.10, lane prior (Now
0.20, reading 0.15, next 0.12, ready 0.08, blocked −0.02, dropped −0.20,
read/unknown 0), frecency weight 0.25 (half-life
frecencyHalfLifeDays), and added-date recency weight 0.10; ties break
on shorter title, newer added, then path. The dropped prior is −0.20 rather than the planned −0.10: at −0.10 a
dropped title-prefix match (prefix bonus +0.15) outscored a Ready
non-prefix word match, failing the "sinks the dropped row" golden.
Newest (RefsListOrder.newest) lists every reference of the scope in
a date-grouped timeline instead of the sections above, with the same
tiered search when a query is typed. ⌘0 changes order, never membership:
the rows are exactly the ones the scope (and, with a query, the T0–T3
search) shows in the ranked order. Rows sort by added day, newest first;
then by added_at instant, newest first, with timed rows ahead of
untimed rows on the same day; then by note path. Rows without an added
date come last, in path order. Consecutive rows form date groups in the
panel calendar (signals.now, signals.calendar), so groups never shift
a day in US evenings:
| Group | Rule | Header | Trailing detail |
|---|---|---|---|
| Today | day ≥ today (future dates clamp here) | TODAY |
Oct 18 |
| Yesterday | 1 day ago | YESTERDAY |
Oct 17 |
| Weekday | 2–6 days ago | THURSDAY |
Oct 16 |
| Month | older, same year as today | OCTOBER |
— |
| Month, earlier year | older, earlier year | DECEMBER 2025 |
— |
| Undated | no added |
UNDATED |
— |
The weekday window matches the caption rule for 2–6 days ago, so a
weekday group and the month group can share a month. Each group is a
RefsSectionKind.added(RefsAddedGroup) with a stable id (day-2026-10-16,
month-2026-09, undated), its title and trailing detail, and an
accessibility label ("Added today, October 18", "Added in December 2025").
Row captions keep their shape; only the date phrase changes: added 9:41 AM
inside a day group (nothing for an untimed row, because the header names
the day), added Oct 7 inside a month group, nothing when undated, and
the ≈ mark stays on git-sourced dates (added ≈ 3:12 PM). Times use
the panel calendar's time zone and locale. The why-here line is the exact
capture date (Added Oct 16 at 9:41 AM, Added ≈ Sep 27 at 3:12 PM · from
vault history, or Undated · bob has no added date for this note);
Newest search keeps the match reason. Section jumps move between date
groups in Newest, searched or not.
NEW¶
A row is NEW when it is Ready or Next (blocked included) and either a
⌥N new mark still holds — no open-log event or Spotlight last-used
date after the mark — or it has no mark, its added day is within 3
local days, and it has never been opened or annotated. A seen mark
hides NEW. Ways out: open the PDF, ⌥N, the reading task leaving
Ready/Next, aging past the window (unmarked rows only), or a successful
Link to Today from Bob Refs. Links made in Obsidian or Capture leave
the row NEW. NEW never runs bob, never writes the vault, and never
changes reading state, task lanes, Today, or review freshness; it is
not the review walk's NEW freshness tier. ⌥N stores the smallest mark
that produces the flipped state, so a second press restores the start
and an arrived row still ages out on schedule.
Tuning¶
swift run refs-rank [--plan plan.json] [--opens open-log.json]
[--new-marks refs-new-marks.json] [--scope chats]
[--now 2026-10-08T09:00:00] [--tz America/New_York]
[--newest] [query...] < ref-list.json ranks a bob ref list snapshot with the
same ranker the panel uses, for tuning against live output on this Mac
or athena. --now accepts a full ISO 8601 time with an offset or a
local date-time without one, interpreted in --tz (default: the current
time zone); that calendar ranks the snapshot. Browse mode prints the
sections; search mode prints
rank tier score (m P W L F R) state kind title with padded columns
(rank 4, tier 9, state 8, kind 7), each followed by its why-here line.
--newest prints the Newest timeline instead, in browse or search: one
## TITLE (count) detail line per date group, then one row per line as
added_at state kind title, where added_at is the instant in --tz
(the bare day when only the day is known, undated when neither), so
bob ref list -R all -A -f json -g | swift run refs-rank --newest
shows the timeline against live output.
--new-marks refs-new-marks.json loads Mac-local NEW marks into the
same ranker.
The target is never bundled: Scripts/bundle.sh copies only the
BobMacCapture product.
The panel¶
RefsPanelController owns a borderless non-activating glass panel
(RefsPanel, a near-full-screen reading room: min(1440, visible
width − 96) by min(1000, visible height − 72), centered on both
axes — 1344 × 798 pt on a 1440 × 870 pt MacBook; the Copy card covers
the list below 760 pt wide). The SwiftUI content sits in an
NSGlassEffectView (corner radius 20); the SwiftUI root itself
carries no glass modifier so the ImageRenderer design fixtures
stay reviewable. Under Reduce Transparency an opaque window
background paints beneath the content. It prewarms at launch
(already at the show size), recomputes its frame on every show,
fades in over 0.12 s while the
content scales from 0.98 to 1.0 on every show (fade only under
Reduce Motion), hides at once with no fade-out, and hides when it
resigns key, like Spotlight. Highlights stays frontmost throughout:
the panel never calls NSApp.activate. The height is fixed while
the panel is visible; filtering never resizes it.
Layout, top to bottom: the 52 pt search bar (magnifier, 20 pt field,
Newest or Tabs token, scope token, "N open · M" or "k of M" count, refresh spinner),
the banner when one is up, the content well (list beside the
inspector on .regularMaterial at radius 12 with a hairline
stroke), and the 30 pt footer (key hints including "⌘S Scan", plus the library
status: "Scanning library…" with a spinner while a scan runs, "Update
failed · ⌘R to retry" when a refresh failed, the scan outcome — "Added
N references", "No new references", or "Scan failed · ⌘S to retry" —
optionally followed by open-PDF clauses such as "1 PDF refreshed in
Highlights" or "1 PDF waiting while you read", "Updating…", or
"Updated …"). The list column takes
min(round(width × 0.52), 560) of the panel width (560 pt at
reading-room widths, 458 pt at the compact 880 pt reference) and the
inspector shows at
760 pt and above. The inspector picks one of two layouts from the
panel width: wide when its column reaches 568 pt (24 pt padding, a
readable column of at most 720 pt centered in the inspector), compact
otherwise (today's 18 pt padding). Rows are 44 pt and two lines: the New dot (accent, only while the
row is NEW),
the kind tile, the rich title (semibold while NEW; code spans styled, matches in the
accent color) with its caption (a stem-only or secondary match
renders its part in the accent color, and a matched author the
caption already shows is not repeated), and the pink Now pill (the
Pomodoro name, or NOW when it is unnamed),
annotation and narration marks, and the state glyph. Section headers
are 26 pt and pinned. Ranked headers read NEW, NOW, READING, …;
Newest headers read TODAY, YESTERDAY, … . The selected row always
stays fully visible
below the pinned header; a show keeps the selected row when it remains
listed, unless a newly added reference resets the view, and a re-rank that
selects the first row returns the list to its
top. Moving up lands the selected row just under its section header. The NEW header leads with the same accent
dot; while a NEW-section row is selected it trails "⌥N Mark Seen"
or "⌥N Mark New". In Newest they are date groups (TODAY,
YESTERDAY, a weekday, a month, UNDATED) with the count after the
title and a trailing day on day groups (Oct 18). The Newest token is
an accent capsule (calendar.day.timeline.left, falling back to
calendar) with the text "Newest" and an xmark; activating it
returns to library sections. VoiceOver reads each row as one element —
"Title, Paper, Ready, added today, 22 pages, Now in BLOG
Pomodoro, new" — with the kind said once and the selected
row carrying .isSelected. The inspector fills instantly from the
list item (hero, kind/state/New/Now chips, title, byline, an
Added/Finished/Pages/Opened/Notes/Reading task/Narration facts grid
with absent rows omitted, the why-here line, and the vault PDF path;
see Bob Refs inspector for the hydrated column); a missing PDF shows an
orange callout reading "↵ opens the note instead". States: 8
skeleton rows on first launch, a centered callout card with Retry
(⌘R) and Copy Diagnostic when no cache exists and the refresh
failed, a centered "No references match" empty state with a quiet second line
("Not in your library yet? ⌘S scans for new references", or "Scanning
for new references…" while a scan runs), and dimmed
unavailable rows that never open whatever slid into their index. A scan
that finishes while the panel is visible re-ranks once like ⌘R, puts
the Just scanned section first with its first row selected, and
announces the outcome to VoiceOver; a scan that finishes while the
panel is hidden posts a macOS notification instead ("Added N
references", or the failure), and clicking it shows Bob Refs with the
section on top. A writing scan that refreshed an open PDF, or that
could not because Highlights did not answer, also notifies or updates
the footer as in Open PDFs in Highlights.
Problem scans raise a banner: a warning with Copy Diagnostic for
per-PDF failures, an error with Scan Again and Copy Diagnostic for
hard failures, and a warning with Scan Again when Highlights needs
you.
| Key | Bob Refs panel |
|---|---|
| Return, Tab, double-click | Open in Highlights (the note when the PDF is missing) |
| Command-Return | Open the note in Obsidian |
| Control-Return | Open the reading task's file in Obsidian, landing on the task line; rows without a reading task beep |
| Option-Return | Reveal the PDF in Finder |
| Up/Down, Ctrl-P/Ctrl-N, Ctrl-K/Ctrl-J | Move (wraps) |
| Page Up/Page Down | Page the results list by one less than fits |
| Ctrl-X | Close the selected tab in Highlights (Tabs only); elsewhere passes through to search |
| Ctrl-D | Scroll the preview down by half its height; while the +N more row is on screen, Ctrl-D shows the next page of highlights at once; search keeps focus |
| Ctrl-U | Scroll the preview up by half its height; search keeps focus |
| Ctrl-C | Reset query, scope, filters, selection, revealed pages, and list and preview positions to fresh-launch defaults |
| Shift-Command-T | Reopen the most recently closed Highlights tab |
| Command-Up/Command-Down, Home/End | First, last |
| Option-Up/Option-Down | First row of the previous/next section (browse and Newest) |
| Command-0 | Toggle Newest: every reference by date added, newest first, grouped by day then month; typing still filters |
| Command-1 … Command-5 | Scope All, Chats, Papers, Articles, Docs |
| Command-R | Refresh library, Today, and git dates now; re-rank |
| Command-S | Scan for new references now (bob ref scan -a -w in the background; an older bob is retried without -a); a second ⌘S while one runs does nothing |
| Command-K | Actions menu for the selected row |
| Command-Y | Open the Copy card for the selected row |
| Command-N | Open the Link to Today card for the selected reference |
| Option-N | Mark the selected Ready or Next reference Seen, or New again; the row stays put until the next re-rank; other rows beep |
| Command-Shift-R | Open the Rename card for the selected row |
| Backspace on an empty query | Remove the Tabs or Newest token first, then the scope token |
| Esc, Ctrl-[ | Link to Today, Rename, or Copy card, then banner, then close while keeping the view |
| Shift-Tab | Consumed (no-op) |
Return and Command-Y while IME-composing pass through to the text system. Ctrl-D,
Ctrl-U, Ctrl-C, and Ctrl-X also pass through while IME-composing. Ctrl-C
always resets the view (even when it already has defaults), and Ctrl-D and
Ctrl-U consume the key even when the preview is hidden, short, empty, or
already at an edge. In Tabs, Ctrl-X closes the selected tab; Ctrl-D always
scrolls. While the +N more row is fully visible in the viewport —
or at the very bottom of the preview — Ctrl-D shows the next page of
highlights at once (clicking the disclosure row does the same);
elsewhere it scrolls half a viewport, and
when nothing is hidden it is still consumed and does nothing.
Shift-Command-T reopens the most recently closed tab, or does nothing
when none remain; it also passes through while IME-composing.
Printable keys fall through to the search field. Result counts are
announced 600 ms after typing stops, never on every keystroke.
Open PDFs in Highlights¶
A writing scan that updates a PDF open in Highlights — editor_saved_first
is true, or the PDF's file name equals an open Highlights tab's
fileName — tells Bob Refs. The tab check uses the existing read-only
Highlights tab reader, only when a coordinated write did not already
save-first and Accessibility is already trusted. It never prompts.
| Event | Bob Refs visible | Bob Refs hidden |
|---|---|---|
| Open PDF refreshed | Footer clause 1 PDF refreshed in Highlights |
Notification "Updated in Highlights" |
coordination_timeout hold |
Warning banner "Highlights needs you" with Scan Again | Notification "Highlights needs you", at most once per PDF until it stops being held |
editor_active / marker_changed hold, or a refresh of a PDF that is not open |
Footer clause 1 PDF waiting while you read (holds only) |
Nothing |
Open-PDF notifications are passive, silent, and grouped
(threadIdentifier org.bobs.bob-mac-capture.refs-pdf-updates) on the
existing refs-scan category ("Show in Bob Refs"). One refresh is titled
Updated in Highlights, with the display title as subtitle and a body
such as Ready → Next · your unsaved highlights were saved first (the
saved-first clause drops when Highlights did not save first; with no
status change the body is Page-1 note updated). Several refreshes are
titled Updated 2 PDFs in Highlights with the titles joined by ·.
A busy hold is titled Highlights needs you, with the display title as
subtitle and body Highlights didn't answer — a dialog may be open. Bob will update this PDF once it's free.
Clicking one opens Bob Refs and selects that reference when it is in
the catalog. Settings › References "Tell me when Bob updates an open
PDF" (on by default) suppresses notifications only; footer clauses stay.
Copy card¶
Command-Y opens an in-panel card for the selected reference. The fixed keys
copy I marker ID, T title, W wiki link, U source URL, L Markdown
link, X arXiv ID, D DOI, N note path, O Obsidian link, P PDF path,
F the PDF file,
R reading-task link, and S research reference. 1–9 copy the visible
commented highlights, including their page and block-link provenance; A
copies all commented highlights and standalone notes plus Bob's own notes,
grouped under ### <Title> (with the section description as a blockquote)
when bob ref show supplies sections, then ### Other highlights for the
ungrouped tail and ### Notes for Bob's own notes. Empty sections and
sections that hold only uncommented quotes omit a group. Older unsectioned
responses keep the previous A payload. H
opens a highlight picker for every highlight and standalone note (digits copy
the visible window, arrows scroll the rest, A copies all highlights). When
sections are present, the picker draws non-selectable group titles before
each visible group and a current-group title when the window starts mid-section;
digits, arrows, wraparound, and counts stay on the entries. Rows
that do not apply are hidden without moving the other keys. Return or Command-Y
copies the selected row (marker ID when available, otherwise wiki link); Up/Down
and Ctrl-P/N/J/K move the
selection, and Escape returns to the inspector. From the picker, Escape
returns to the card.
Home-folder paths copy with ~ (for example, ~/bob/lib/…); only the
Obsidian link and the PDF and narration file copies stay absolute, needing
a real path.
Hold Option (⌥) to swap previews in place. Option-W copies a titled wiki link,
Option-P the absolute PDF path, Option-N the note's full ~ path, and
Option-F the narration file when it exists. Option-1–9 copies the highlight's
quote verbatim, without comment or provenance, on either the card or picker.
Rows without an alternate keep their normal payload, including standalone notes.
Option-click and Option-Return copy the displayed alternate too. A queued note
copy remembers whether Option was held; Return retries the same payload after a
failed write, even if Option has since been released.
Each row previews the exact value it copies. A shared marker ID shows its
shared ×N count; press N to copy this note's path. If the installed Bob
does not report marker_id, I explains that a newer Bob is needed and the
other rows still work. A successful copy checks the pasteboard write, hides
Refs, and shows a glass HUD. When Refs was opened over a Capture draft,
Capture returns with that draft ready for ⌘V. A failed write leaves the card
open so Return can retry.
Rename card¶
Command-Shift-R or Rename… in the ⌘K menu opens a card for the selected
reference. Edit the filename stem; the .pdf suffix stays fixed. Return
runs bob ref rename for the selected note, and Escape closes the card.
Ordinary typing and editing shortcuts stay in the filename field. Bob's
refusal message and hint appear inline, and the draft stays editable for
another attempt. The card explains that the note and inbound links move
with the PDF while the reading task keeps its identity. Close the PDF in
Highlights first. Hiding the panel does not cancel a rename already in
progress; completion while hidden posts a notification.
Link to Today card¶
Command-N or Link to Today… in the ⌘K menu opens the selected reference's Pomodoro picker. Bob supplies the open Pomodoros, the current or next default, and each preview; Return links to the default, while typing filters rows and offers a new Pomodoro name. Arrow keys move through matches, Shift-Return creates or reuses the exact typed name, and clicking a row links it. The card stays open while choosing another reference. The selected row gains its Now pill immediately, then the library refresh replaces that optimistic pill. A successful (non-guarded) link from Bob Refs marks that row seen, so it leaves NEW on the next unfrozen ranking. A glass toast confirms the lane and ledger change; if the panel was hidden when Bob returned, Bob Refs posts the same outcome as a system notification. VoiceOver announces visible successes. Escape closes the card, and a link already in progress finishes if the panel hides.
Opening Bob Refs¶
- From anywhere on the Mac, Control-Shift-Command-O toggles the panel. Settings › References can turn this binding off.
- While Highlights is frontmost, Command-O opens Bob Refs instead of Highlights' Open… dialog. Settings can switch the takeover to Control-O or turn it Off. The takeover registers only while Highlights — or the override app picked in Settings — is frontmost, so Command-O and Control-O keep working everywhere else, and Highlights' File › Open… still opens its own dialog. The panel never activates, so Highlights stays frontmost and the same key closes the panel again.
- While Highlights is frontmost and Bob Refs is hidden, Command-T opens Bob Refs in Tabs (default) from fresh-launch defaults, as after Ctrl-C. Settings › References can change this key to Shift-Command-O, Command-E, or Off. It registers only for Highlights or the override app; the global Control-Shift-Command-O and Command-O library flows are unchanged. While Bob Refs is showing, Command-T switches between Tabs and the library instead of closing the panel; Escape closes it.
- Ordinary shows restore the remembered query, scope, filters, selection, revealed pages, and scroll positions, except when a newly added reference resets the view and selects it (including an arrival in the first 10 seconds of an untouched show). A Ctrl+Tab switch always restores the remembered view and never resets for an arrival. The Highlights Tabs key resets query, scope, Newest, selection, revealed pages, and list and preview positions, then turns Tabs on and selects the Tabs preselection. A Tabs show defers a new-reference reset to the next ordinary show. Ctrl-C resets the view to fresh-launch defaults.
- Clicking a scan notification opens the default view with Just scanned at the top and its first row selected. Clicking an open-PDF update notification opens Bob Refs and selects that reference when it is in the catalog.
- The menu-bar item offers Bob Refs… directly below Capture, with the same shortcut shown for display.
- Only one Bob panel is ever visible: opening Refs while Capture is
open retains the capture draft first (
BobPanelCoordinator), and opening Capture hides Refs. Ctrl+Tab switches between the panels through the same coordinator. - Every successful capture refreshes Today and marks the snapshot stale, so the next open re-reads it. Recheck Bob re-points the Refs fetcher, restarts its watcher, and refreshes the snapshot alongside the capture ones.
- Settings › References also shows the resolved Highlights app (name,
version, and path, or "Not found"), with Choose… (limited to
.app) and Use Default, plus Reset Open History… behind a confirmation dialog. It also sets Preview highlights per page (1–20, default 5) for the inspector, ordefaults write org.bobs.bob-mac-capture refsInspectorNotesPageSize -int 5, and the "Tell me when Bob updates an open PDF" toggle (on by default; off suppresses notifications only). - Diagnostics: a failed global or takeover registration reports "Refs hotkey conflict: …"; free the shortcut and use Recheck Bob. Toggling the References settings re-registers immediately.
Tabs¶
Tabs lists every document open in Highlights. A matched tab uses its reference note row. A collision or a file outside the library stays in the list as a dim row with its filename and a clear match status.
Press Command-T while Highlights is frontmost and Bob Refs is hidden to open Tabs; Settings › References can change this to Shift-Command-O, Command-E, or Off. Inside Bob Refs, Command-T toggles Tabs; the filter combines with kind scopes and keeps the current query. The current tab comes first; the rest follow most-recently-active order, tab-bar order breaking ties, unmatched tabs last. When Highlights is frontmost, the previous tab is preselected.
A Tabs show keeps the selected row while that tab is still open. After Bob Refs switches to a tab, the next Tabs show preselects the current Highlights tab once. Command-T from Highlights always starts fresh.
Return, Tab, or double-click switches to the existing tab. Switching keeps its page, does not change reading state, and does not add an entry to the open log. On a collision or unmatched Tabs row, Command-Return and Control-Return show the same warning instead of opening. Control-X closes the selected tab, including collision and unmatched rows: the row folds out, the next row is selected, and the panel stays up for repeated closes. Hovered and selected Tabs rows also show a Close Tab button (⌃X). A background tab closes by briefly switching Highlights to it, closing it, and switching back. Closing the last open document also quits Highlights, and the panel hides. Shift-Command-T reopens the most recently closed tab (up to 20, kept in memory for the app's lifetime). A matched row reopens its library PDF; a collision or unmatched row reopens only when its full path was known.
If Highlights already closed the tab, the row stays gone with no error. If a save sheet appears instead of closing, Bob Refs hides so the sheet is in front. Any other failure restores the row, selected, with "Couldn't close “title” in Highlights."
Tabs refreshes on panel show, Command-T, or Command-R; it does not poll in the background. The panel explains when Accessibility permission is needed, Highlights is not running, no documents are open, or a read fails.
Accessibility reads Highlights' window and tab attributes, selects a tab through its button, and presses a window's close button. Names stay in memory. Highlights exposes the visible tab's full path; background tabs match by filename only. Tabs in other Spaces and full-screen windows are untested.

