Read a PDF, select text, press a key, write what is wrong with it. Every comment is saved to a JSON file together with the exact text and position it refers to, so a script or a coding assistant can find the right place in the source.
just-doc book.pdf # open for commenting
just-doc book.pdf --read # print the comments
just-doc book.pdf --export # write book.commented.pdf with the comments inside
just-doc notes.md # a markdown file works the same way
just-doc slides.html # and an HTML file, such as a slide deck
just-doc # list the open documents; open the window again if it was closed
just-doc book.pdf --wait # open it, wait for Ctrl+Enter, then print the comments
just-doc book.pdf --reply 2 "text" # add a reply to comment 2
just-doc --guide # how a coding assistant reviews a document with youWith uv:
uv tool install git+https://github.com/ladisk/just-docor with pipx: pipx install git+https://github.com/ladisk/just-doc.
To try it without installing: uvx --from git+https://github.com/ladisk/just-doc just-doc book.pdf.
Needs Python 3.9 or newer; the only dependency is pypdf, used for the export. The window is a Chromium, Brave or
Chrome app window if one is installed, otherwise a tab in the default browser.
| key | action |
|---|---|
a |
highlight the selection and write a comment; on a selected comment, edit it, or once it is answered or done, reply; Enter in an empty field leaves a plain highlight |
A |
reply to the selected comment, the one of the open card or the one under the cursor (see "Reviewing with a coding assistant") |
E |
edit your comment; the assistant does not see an edit after it answered, so reply then |
za |
open or close the row of the comment in the list, or on a change the group of changes; zR / zM open / close all rows |
D |
show or hide the comments that are done |
m / M |
mark what changed since the last hand-over / since the last commit; the same key again hides the marks (see "What changed") |
a on the card of a change |
comment on that change |
gb |
show the page at the cursor as it was before the changes; gb or Esc goes back |
gh |
history: the rounds so far, each with what you handed over, the answers and the changes; Enter shows the document as it was at the end of a round (see "Rounds and history") |
Ctrl Enter |
hand the comments over to the just-doc --wait that is waiting for them |
s |
strike through the selection: this text should be deleted |
r |
draw a rectangle (for a figure, an equation or code) and comment on it; a click with no drag puts a point instead, for a place with no text |
d |
delete the selected highlight or rectangle (click it first) |
u |
undo the last delete |
c |
hide or show the comment list, so the page gets the full width |
C |
hide or show the highlights and the link marks on the page, to read the clean text; the selected one stays visible |
n / p |
go to the next / previous in the comment list, top to bottom: the answered comments first, then the open ones, then the changes; done comments of earlier rounds are skipped unless D shows them; a card shows the reply, your comment and the changes near it (see "What changed"); gn / gN do the same |
Enter |
follow the link under the cursor (a click on a link does the same); Tab / Shift Tab move the cursor to the next / previous link on the page |
K |
preview the target of the link under the cursor: a crop of the target page in a popup (resting the mouse on a link does the same); j / k or the mouse wheel scroll it within the target page, a click on it goes there, Esc or K closes it |
Ctrl o / Ctrl i |
jump back to where you were before a jump / forward again; the back / forward buttons of the mouse do the same |
/ |
search the whole PDF: type the text, Enter; then n / N go to the next / previous match and Esc ends the search |
t |
table of contents: j / k move, Enter goes there, Esc closes |
h j k l, w b e, 0 $ |
move the cursor (see below); the page follows it |
↓ / ↑ |
move the cursor a line down / up, as j / k do; also in a selection |
f / F + a character |
move the cursor to that character after / before it on the line (fa, 3fa); the wait for the character ends after 1.5 s |
; / , |
repeat the last f or F / repeat it the other way |
gg / G |
go to the top / bottom of the document, with the cursor on the first word / the last line; with a number first (12G) go to that page |
Ctrl d / Ctrl u |
half a page down / up |
zt / zz / zb |
scroll so that the cursor line is near the top / in the middle / near the bottom of the window, with about two lines above or below it; also in a selection |
← / → |
go to the previous / next page |
P |
single page view: show one page at a time, fitted to the window; P again goes back to the continuous view and the zoom from before |
v / V |
start a visual / visual line selection (see below) |
Ctrl + wheel, Ctrl + / - / 0 |
zoom the page around the pointer; the toolbar and comment list keep their size |
Ctrl S |
export a copy of the PDF with the comments inside |
Ctrl Shift S |
markdown or HTML file: save the PDF made from it as notes.pdf next to it, without comments |
H / L |
previous / next tab, when several documents are open; Alt ← / Alt → do the same |
Ctrl w |
close the tab; Alt w does the same |
q then y |
stop just-doc and close its windows; a --wait that is waiting ends too. Closing the window alone leaves just-doc running, and just-doc without a file opens the window again |
? |
show or hide the full key list; in it / filters the keys by what you type |
Esc |
deselect |
A comment, a reply and an edit are written in a field at the bottom of the
comment's card, under its text. Enter saves, Shift+Enter starts a new
line, Esc cancels.
The browser's own shortcuts (print, find, history, bookmarks and so on) are
switched off in the window. Copy (Ctrl+C), reload (Ctrl+R, F5), full
screen (F11) and the developer tools (F12) still work.
The first just-doc book.pdf opens the window. While it runs, every further
just-doc other.pdf adds the document as a new tab of the same window and
returns at once. H and L change the tab and Ctrl+w closes it; the
tab bar shows only when more than one document is open. Each tab keeps its
page, zoom, cursor and search.
All tabs belong to the first command, so stopping it with Ctrl+C closes
them all. If the window was closed but the command still runs, the next
just-doc opens a new window with the tabs.
A blue block marks the cursor. It moves as in vim, so no mouse is needed:
| key | move |
|---|---|
h / l |
one character left / right |
w / b / e |
next word / start of the word / end of the word |
j / k |
one line down / up |
0 / $ |
start / end of the line |
A number first repeats the move: 3w, 5j. The page scrolls to follow the
cursor, and when you scroll the page the cursor stays on the screen. A click
puts the cursor on the clicked character.
v starts a selection at the cursor; V does the same but selects whole
lines. The same keys then extend the selection, also onto the next page, and
o switches to its other end. Press a to comment on the selection or s to
strike it through. Esc cancels. Text selected with the mouse (a drag, a
double or a triple click) is the same selection: the cursor is at its end and
the keys go on from there.
In a selection, i and one more key select the thing around the cursor:
| keys | select |
|---|---|
iw |
the word |
is |
the sentence |
ip |
the paragraph |
i( i[ i{ i< |
the text inside the brackets around the cursor |
i" i' |
the text inside the quotes, straight or typographic |
So vis a comments on the sentence under the cursor. The selection can be
extended with the usual keys afterwards.
In vim, a is the "around" form (va( takes the brackets too). Here a in a
selection writes the comment, so va( does not exist; the capital A takes
its place: vA(, vA".
A sentence ends at ., ? or ! before a capital letter, so it can stop too
early after an abbreviation that is not known. A PDF stores lines and not
paragraphs, so a paragraph is guessed from a larger gap between lines, an
indented first line or a change of the text size. Both stay on one page. A
margin note next to the text is left out of the sentence or paragraph, and it
is a paragraph of its own.
c and P remember their state between runs.
In the single page view, j on the last line and k on the first line turn
the page, and so do PageDown and PageUp. A selection stays on its page.
Links inside the PDF (a reference to an equation, a citation, an entry of the
printed contents) are marked with a blue underline and work: put the cursor on one and press Enter, or click it.
To look at the target without going there, rest the mouse on the link or press K: a popup shows that part of the target page. The wheel or j / k scroll it
up and down within that page.
Ctrl+o then returns to the place of the link, with the cursor where it was.
The same key returns from every other jump: a search, the contents panel,
n / p, gg, G. Ctrl+i goes forward again. A web link opens in the
browser.
/ searches all pages, not only the ones on the screen. Lower-case text
matches both cases; a capital letter in the query makes the search exact. The
cursor moves to the match, so v can start a selection there. While a search
is active, n and N step through the matches and not through the comments.
book.pdf → book.comments.json, in the same folder, rewritten after every
change. Each entry holds the selected text, the comment, and rects: page
number p and position in PDF points measured from the top-left corner.
An entry with kind strike is struck-through text that should be deleted,
kind box is a rectangle, and kind pin is a point: its one rectangle has
no size, text is empty, and near holds the text line just above it. An
entry without kind is a highlight.
before and after hold the letters and digits around the text, to find it
again in a changed document (see "When the document changes"); was is the
text as it was when it has changed since, and lost marks a comment whose
text is not found any more. change marks a comment on a change (a on
the card of a change, see "What changed"): it holds the text as it was before the change, and
an empty text with a change means that the text was removed.
A comment can have replies, a list of entries with text, the time at and
from, the name given with --reply. A reply without from was written in
the window. done marks a comment as done, and done_at holds when. seen
lists the times a just-doc --wait showed the comment to the assistant.
A coding assistant (Claude Code, opencode, pi or any other that can run shell commands) can answer your comments in the window and fix the source, so the whole review happens in just-doc. Tell it:
Run
just-doc --guide, then reviewnotes.mdwith me.
just-doc --guide prints the instructions for the assistant: which commands
to run and how to read their output. Nothing is set up for a particular
assistant; it only runs the three commands below.
The round goes like this:
- The assistant runs
just-doc notes.md --wait. The window opens. - You write comments and press
Ctrl+Enter. The message at the bottom says whether somebody is waiting for them. - The assistant replies. Each reply shows at once in the list. A blue mark
in the toolbar counts the comments with a new reply, and so do the tab and
the title of the window. The answered comments come first in the list,
and
ngoes through them from the top; a comment that you go to is no longer new. While no reply has come, the mark says that the comments are handed over; while ajust-doc --waitwaits for you, a green mark says so, and an amber one says that no--waithas taken yourCtrl+Enteryet: the assistant has to run--waitagain. The replies come one by one while the assistant works ("the assistant is answering … 2 replies so far"); when it waits again, its answers are complete: a large green mark says "✓ answers ready", a message pops up, and the title of the window and the tab get a ✓. The changes in the document are marked too, and come last in the list (see "What changed"). - To answer a reply, press
Aon its card (or on the selected comment), write in the field that opens at the bottom of the card, and pressCtrl+Enteragain. - When you agree, the assistant changes the source. The window loads the new version by itself and the comment is marked as done.
Each row of the list is two lines: a mark that says where the comment stands,
the page and the text;
then who spoke last and what. The selected comment has a red frame and its row
is open, with the whole conversation; za opens or closes a row, zR / zM
all of them. Below the comments, one closed line counts the changes that have
no comment; the row that n is on shows under it, and za on a
change (or a click on the line) shows them all.
The marks (the ? key list explains them too):
| mark | meaning |
|---|---|
| not handed over | written or answered after your last Ctrl+Enter |
| not seen yet | handed over, but no --wait has shown it to the assistant yet |
| seen 10:42, no reply | the assistant was shown it then and neither answered nor marked it done |
| new reply / new: done | the assistant answered or marked it done, and you have not looked yet |
| answered | the assistant spoke last and you have looked at it |
| done ✓ | done; it leaves the list at your next Ctrl+Enter |
| change | a change since the last hand-over that no comment asked for |
When the assistant is shown a comment again that it left unanswered before,
--wait marks it with "(shown to you before and not answered)".
just-doc book.pdf --waitopens the document if it is not open, waits for Ctrl+Enter in the window,
prints the open comments and ends. A comment is open when it is not done and
you spoke last. A Ctrl+Enter pressed while nobody waits is kept for the next
--wait. With --timeout 100 the command gives up after 100 seconds with
exit status 2, for an assistant whose shell limits the run time; it then runs
the command again.
just-doc book.pdf --reply 2 "it converges only for a simple root" --from Anaadds a reply to comment 2: its number in the --read output, or its id from
--read --json. --from is the name shown on the reply. --reply can be
given several times in one command. The export writes the replies into the
note of the highlight.
just-doc book.pdf --done 2marks comment 2 as done (done in the JSON file). Its highlight fades and
it is greyed in the list, under "answered since your Ctrl+Enter". At your
next Ctrl+Enter the done comments that you have looked at leave the page
and the list, so each round starts with what is still open; nothing leaves
before that. D shows them again, and the
top of the list says how many there are. The card says whether the comment is
done. Answering a done comment opens it again, so the next
--wait prints it. --done can be combined
with --reply and given several times.
--read prints all comments with their replies, and marks each one that is
(done) or (answered).
When the file of an open document changes on disk (the markdown or HTML file was edited, the PDF was compiled again), the window loads it again by itself and stays on the page. It then looks for the text of every comment in the new version:
- Text that only moved, to another line or page, gets its highlight at the new place.
- Text that was changed is found by the text around it. What stands there
now is marked green, and the list shows both:
«teh»→ «the».--readprints the new text and the old one. The card shows the old text above the replies. - When the text around it is gone too, the comment keeps its old place and the list says "text not found". A short text, such as one word, is not looked for on other pages, because the same word stands in many places.
- A comment on a page that the new version does not have (it got shorter) goes to the end of the last page, also with "text not found".
Each comment remembers 120 letters and digits before and after its text for
this. A changed text next to another changed text is found as one piece, so
two comments can then point to the same new sentence. A rectangle or a point
(r) stays where it was drawn. The search runs in the window, so a document that changed
while it was not open is handled when it is opened next.
After a round you see the new document, but only the places you commented on.
After your first Ctrl+Enter everything that is not as it was then is marked:
changed and new text is underlined in blue, and a red line stands where text
was removed. A change has only a line and no fill, so it is not mistaken for
a comment, which is filled. m hides the marks and shows them again; M marks everything
that is not as it was at the last commit instead. The marks stay on when the
window loads a new version of the file.
A change with a wavy line was not asked for: no comment of the round is within two
lines of it. These are the ones to read first, when an assistant changed more
than you asked. A comment of the round is an open one, or one that was done
since your last Ctrl+Enter. A change near a comment belongs to it.
n and p go through the comment list as it is shown, from the selected
comment (or the first one after the cursor): the comments that the assistant
answered (or marked done) since your last Ctrl+Enter, the open ones, and
last the changes that have no comment. The comments that were done before
that are skipped, unless D shows them; changes a few lines
apart on one page are one stop, with a card that lists them. n then p
comes back to the same place; a click on a comment, in the list or on the page,
opens its card too. A card beside the page (under the text when the margin is narrow) says who replied and what, with your comment above in
grey, and shows the changes that belong to it, each as a sentence with the old
words struck out in red and the new ones in green; the other changes have
cards of their own, which name your comment only when it is still open. The card says where you are ("3 of 7"); the toolbar counts what you have not looked at yet,
and what you have seen fades. Esc closes the card. On the card of a change, a comments on the change
itself: the box and the comment keep the change in view, and --wait shows
the assistant the text as it was before.
Words are compared, so a change in an equation, a figure or the layout is
easy to miss. gb covers the page at the cursor with the same page of the
old version; press gb again to flip back and see what moved.
The words of the two versions are compared in reading order, so text that
only moved to another line or page is not a change. Page numbers and running
heads are left out. Figures and the layout are not compared (use gb).
For "the last commit":
- A file that is in git (a markdown or HTML file, a PDF that is committed) is taken from the last commit.
- A PDF that is built from a source and is not in git has no version there. just-doc keeps a copy of it whenever a commit is made in its repository while the document is open, so compile the PDF before you commit. A PDF that is older than the last commit counts as that version too.
The copies are kept in the cache folder (~/.cache/just-doc).
Every Ctrl+Enter starts a round, and just-doc keeps a copy of the document
as it was then. The comment list puts what is new on top: the comments not
handed over yet, then the ones answered since your last Ctrl+Enter, then
the open ones; the older ones are greyed at the end.
gh opens the history, newest round first. j / k move through the rounds,
and the selected one shows the comments and replies you handed over, the
answers that came back, and the changes in the document from that
Ctrl+Enter to the next one (for the last round, to now). A click on a
comment goes to it.
Enter shows the document in the window as it was at the end of that round,
as if it were then: the version of the next Ctrl+Enter, the changes of the
round marked, and only the comments and replies there were by then. The
toolbar turns purple and says which round it is. This view is read only;
gh and Enter on the last round go back to now. Esc closes the history.
The comments are not stored in the PDF, so other viewers do not show them.
Ctrl+S (or --export) writes book.commented.pdf: a copy of the PDF with
every highlight, strike-through, rectangle, point (a sticky note) and comment as an ordinary PDF annotation, readable in Okular,
Evince, Acrobat and others. The original PDF is never changed. The JSON file
stays the working copy; export again after adding comments.
If book.synctex.gz is next to the PDF (compile with -synctex=1), --read
adds the .tex file and line for each comment:
[1] p. 37 /path/chapters/chapter_02.tex:1228
text: the roots are found with a method that always converges
comment: too strong, say when it converges
The lookup uses the position of the comment in the PDF. After compiling again
the window moves the comments to their text in the new PDF (see "When the
document changes"), so the lines stay right as long as the document is open
in the window, or is opened once before --read.
just-doc notes.md converts the file to a PDF with pandoc
and opens that, so pandoc must be installed (with typst
or a LaTeX installation to make the PDF; typst is used when it is found). The
PDF is kept in ~/.cache/just-doc and made again when the markdown file has
changed. Comments go to notes.comments.json next to the markdown file, and
--export writes notes.commented.pdf there. Ctrl+Shift+S saves the PDF
without comments as notes.pdf next to the markdown file, replacing a
notes.pdf that is there.
A ```mermaid block is drawn as a diagram when mmdc
(mermaid-cli) is installed;
without it the block shows as code. The text in a diagram can be selected and
searched like the rest of the page.
--read adds the line in the markdown file for each comment. It finds the
line by searching for the selected text, so it also works after the file was
edited elsewhere; a text that occurs more than once gets its first line and a
note:
[1] p. 1 notes.md:3 (first of 2 places)
text: always converges
comment: too strong
After the markdown file was edited, the highlights move to their text in the new PDF (see "When the document changes").
just-doc slides.html prints the page to a PDF with the browser that shows the
window (Chromium, Brave or Chrome, run without a window) and opens that. The
rest is as for a markdown file: the PDF is kept in ~/.cache/just-doc and made
again when the HTML file has changed, Ctrl+Shift+S saves it as slides.pdf,
and --read adds the line in the HTML file for each comment. Only the text of
the page is searched, not the tags, scripts, styles or title; text that a
script writes has no line.
The page decides the paper and the page breaks with its print rules, so a slide deck needs them to get one slide per page:
@page { size: 1280px 720px; margin: 0 }
section { page-break-after: always }A reveal.js deck does this by itself: the page is
opened as slides.html?print-pdf, which is reveal's print view. Scripts run
for up to ten seconds of page time before the page is printed. Pictures and
styles are loaded as the page links them, also from the internet.
A change in a linked file (a style sheet, a picture) is not seen; save the HTML file again to make the PDF again.
web/pdfjs/ is a copy of PDF.js 4.10.38
(Apache License 2.0, see web/pdfjs/LICENSE).