Capture from your phone (the PWA)#
scrap serves a web app (a PWA) at the root of its address, built for one-handed use on a phone. It has four tabs:
Tab |
For |
|---|---|
Capture |
The composer, camera and gallery, the share target, the offline queue |
Todos |
What to do now, soon or ever |
Search |
Every page, newest first, with kinds, filters and tags |
Topics |
The topic pages Claude keeps |
Opening a page gives a page view with an editor, photo and drawing viewers and, with Claude on, what Claude made of it.
Jump to: install, capture, share, offline, navigate, search, tags, todos, page view, editing, Claude’s output, topics, iPhone, developers.
Install it on Android#
Open scrap’s HTTPS address in Chrome. A PWA needs HTTPS (
http://127.0.0.1also works on the server itself). Get an address from thetailscale serveURL in Deploy on a single server, or the Tailscale operator or an ingress in Deploy on Kubernetes.Chrome menu, Add to home screen (or Install app), then Install.
Open scrap from its icon. It opens on the capture screen, in its own window.
Installing is what puts scrap in the Android share sheet.
The app’s text is sized like a web page’s, so Chrome’s zoom enlarges it. To follow the phone’s Font size instead, or to make scrap smaller or bigger than other sites, see Text size.
scrap has no login of its own: anyone who can reach its address can use the app, so reach it only over your tailnet or behind a proxy that authenticates (see HTTP API and CLI).
Capture#
Button |
Does |
|---|---|
Camera |
Opens the camera |
Gallery |
Picks one or more images (photos only, so the phone opens its photo picker) |
File |
Picks one or more PDFs, text ( |
Note |
A text box; a pasted link alone becomes a link capture |
On a computer Camera, Gallery and File would all open the same file dialog, so there are two buttons instead: Add files (photos, PDFs, text and Markdown in one pick) and Note.
Save returns at once: the capture is stored on the phone first and uploaded in the background. The four newest pages are listed above the buttons.
flowchart LR
A["Camera / Gallery / File / Note"] --> B["Composer"]
S["Android Share"] --> Q
B -->|Save| Q["Queue on phone<br/>(IndexedDB)"]
Q -->|"online"| U["POST /api/captures"]
U --> P["Page on server"]
P -.->|"ingest on"| C["Claude reads it"]
classDef user fill:#e9f7ef,stroke:#2e9d5b
classDef store fill:#e8f1ff,stroke:#4a7bd0
classDef claude fill:#fff3e0,stroke:#e08a00
class A,B,S user
class Q,U,P store
class C claude
The composer#
Kind. A row of chips above the text sets the page’s kind: Note (default), Todo, Later (to read, watch or listen to), Board (whiteboard) or Doc. A link on its own picks Later until you pick something else. With photos, left as Note, Claude reads them and makes a whiteboard or document a page of that kind. A kind you choose is kept; Claude never changes it. You can change it later in the editor and nothing is lost (status, due date and summary stay, so changing back restores the page).
Todo. With kind Todo (photos and files too) the composer adds Due (Today, Tomorrow, Next week or Pick a date…) and Priority, and asks What needs doing?.
Folded lines. Due and Tags are each one line above the text (Due 24 Oct · high, Tags 2 · #home #garden); tap to open, tap again to fold.
Photos and files. Camera and Gallery add more photos (to a note too), and File PDFs, text and Markdown files. Each goes in the text where the cursor is, as a token (
⟦image 1⟧,⟦file 1: minutes.pdf⟧) on a line of its own, with a chip under the text box, as in Edit: tap a chip to select its token (cut and paste it to move the photo), or ✕ to remove it. Deleting a token removes its photo or file too (also from the draft: one cut to move it is kept once pasted back). All of them and the text become one capture and one page, each photo or file where its token was. A file’s text is read for search. On a computer one Add files takes their place, picking photos and files together.Upload limit. Photos and files together over the server’s limit (50 MB,
SCRAP_MAX_UPLOAD_MB; the app learns it from/healthz, so this works offline) make Save say so: remove some, or Save separately (one capture per photo or file, the text, the kind and a todo’s due date on the first, with only that one’s token). A proxy may have a lower limit.Tags. The Tags line opens a tags box as on a page (Tags). The page is made with its tags, so search finds it at once, also when saved offline. Your tags on a photo are kept and Claude’s join them on its first read (a later Regenerate adds tags only to a page with none). Saved separately, every photo’s page gets the tags.
Drafts. Leave the composer with the phone’s Back and what you typed is kept as a draft: a row over the buttons (Draft:, its first line and what it holds: Trip · 2 photos, 1 file, as a waiting upload’s row says it). Tap it to carry on, or ✕ to discard (with Undo). The buttons and New todo always start fresh and replace an older draft, with Undo to put it back. One draft at a time. It is also kept as you type, so a closed or killed app brings it back. A capture still open in another browser tab is left alone: it becomes the draft only when you leave it, or once that tab is closed. Several left behind (tabs closed mid-capture) come back one per open, each over the last with Undo. Tabs share the one draft, so an Undo in an older tab can replace a newer draft from another.
The offline queue#
stateDiagram-v2
[*] --> Waiting: Save / Share
Waiting --> Uploading: online, app open, timer
Uploading --> Done: 2xx
Uploading --> Waiting: network error, 5xx, 408, 429
Uploading --> Failed: other 4xx
Failed --> Waiting: Retry
Failed --> [*]: Discard
Done --> [*]
Idempotent. Every capture gets a ULID on the phone before it is queued, so a retry after a lost answer saves it once.
Storage. The queue is in IndexedDB (photos made smaller, see Smaller photos) and survives closing the app and restarting the phone.
Badge. The header badge shows what is on the phone (2 waiting · 1 failed); tap it to retry now. Waiting items are listed oldest first at the top of the capture and search screens: waiting to upload with the last problem (offline, server error (503), connection lost, timed out), or not saved with the server’s reason. A capture saved online says Saved at once; if the server then refuses it within ten seconds, a second toast says Kept on this phone and its row there says why, with Retry. Offline, the capture screen shows the badge, the waiting rows and Offline: showing what’s on this phone in place of the newest pages; the pages return when the phone is back online.
When it uploads. At once, when the phone comes back online, whenever the app opens or comes to the front (another tab’s upload first), and on a timer with backoff (2 s doubling to 5 minutes) while open. Android has no reliable background upload, so a capture made offline uploads the next time scrap is open with a connection. Uploads go oldest first, one at a time.
Failures. Network errors,
5xx,408and429retry by themselves. Any other4xxis permanent: the capture is marked failed with the server’s reason (refused: or too large:, for example413) and stays with Retry and Discard (asks first; deletes it and its photos). A failed capture is not retried until you tap Retry or the badge. A multi-photo capture refused as too large offers Split and retry: one capture per photo, text on the first, tags and kind on every one. These buttons wait for an upload in progress (Waiting for upload…).Dates. A page from a queued capture is dated when the server receives it, so
date:created,after:andbefore:use that day. The phone’s time is kept on the capture (created_atin the API) and, for photos or files alone, in the page’s first title (UTC, such as2026-10-22 13:04) until you retitle it or Claude titles it.Timeouts. A slow upload may take up to 15 minutes while it keeps moving; one with no progress for a minute is given up and retried. After the whole upload is handed over, the phone waits at least two minutes for the answer.
Storage full. The composer stays open with your text and photos and says so. A share that cannot be stored goes straight to the server (
POST /share, see HTTP API and CLI), or shows a page saying it was not saved if there is no connection either.Tabs. Two open tabs take turns (Web Locks), so a capture is never uploaded twice at once.
Text size#
Settings → Text size sets how big scrap’s text is on this device. 100%, the default, is scrap’s usual 17 px, sized as a web page is, so Chrome’s zoom enlarges it as it does every other site. 80%, 90%, 115% and 130% are 17 px times that. Phone follows the phone’s font size instead (Android’s Font size, or an iPhone’s Text Size and Larger Text), never smaller than 17 px. Everything sized from the text follows it (buttons, chips, the editor). The choice is kept on this device, applies at once, and is applied again whenever the app starts or comes back.
If scrap looks much bigger in the installed app than in a Chrome tab or another app, check Chrome’s own zoom: Chrome, Settings, Accessibility, Page zoom (and Site settings, Zoom, for scrap’s address). It applies on top of the font size.
Maximum column width#
On a wide enough window (760 px or more: a computer, or a phone turned sideways), scrap centres a column rather than stretching every line the width of the window. Settings → Maximum column width sets the most it grows to: Narrow (720 px), Medium (800 px, the default), Wide (880 px) or Wider (960 px). A window narrower than the chosen width still shrinks the column to fit, with the usual gutter either side, so nothing scrolls sideways. It is the window’s width that decides this, not the device: a narrow phone never reaches it.
Smaller photos#
Before a photo goes into the queue, the phone makes it smaller: at most 2560 px on its long edge, as a JPEG at quality 85, turned upright (the camera’s rotation is applied and the EXIF data is dropped). A big photo then holds the connection for less time, and the queue takes less room. Claude reads a 2048 px version anyway, so little is lost, and 2560 px leaves room to zoom into a whiteboard.
Only JPEG, HEIC, HEIF and WebP photos are made smaller. A photo is sent as it is when it is at or
under 2560 px already, when the browser cannot decode it (HEIC on some phones), when making it
smaller fails, or when the result would not be smaller; PNG, GIF, PDFs and other files are never
touched. The name is kept, and so is the capture time; a .heic photo becomes a .jpg (its name’s
extension changes, since the bytes are now a JPEG).
Known limits:
The upload limit is checked on the size the photo has when you pick it, before it is made smaller. A photo over the server’s limit is refused even if it would fit once smaller.
A photo shared into scrap from another app (Share) is queued as it came: the share target runs in the service worker, which does not make photos smaller.
The full-size original stays in the phone’s own gallery, not in scrap. Colours are redrawn in sRGB, so a wide-gamut colour profile may be lost. Settings, Photos, Full size turns this off for every photo (Smaller is the default); there is no choice per shot.
Public pages in Settings#
Settings, Public pages, Refresh N pages draws every public page again and publishes it, then rebuilds the site’s home page, tag pages and search once: for changes the app did not see, such as an edit over MCP, a new write-up or a new version of scrap. It says Refreshing 3 of 12… as it goes, then Refreshed 12 pages at 14:05 (and any it could not, with why). If some public pages have never been published, it lists them first and asks once: These pages will go public for the first time; Cancel refreshes nothing. With no public pages it says so: No pages are public. Make one public from its Share menu.
What scrap stores on the phone#
All of it stays in the browser, for scrap’s own address, and is never sent anywhere.
Where |
What |
|---|---|
IndexedDB |
The upload queue, and the drafts (composer, page edit, drawing edit) |
Local storage |
Recent searches, whether Claude’s Description and Text in the photo are folded, the debug panel switch, the server’s upload limit |
Cache Storage |
The app’s own files only, in a cache named |
Clearing site data for scrap’s address (or iOS evicting it) deletes anything queued and every draft. If the browser blocks local storage (a private tab), scrap carries on without it: no recent searches, remembered folds or offline size check.
What works offline#
The app opens without a connection. Anything that reads from or writes to the server needs it.
Works offline |
Needs the server |
|---|---|
Opening the app, the capture screen, the badge |
Opening any page |
The composer, with photos and tags, and its drafts |
Search results and the Todos screen |
Saving a capture (queued) |
Changing tags, kind or due date |
Sharing into scrap |
Ticking a task box on a page |
The editor on a page already open, and its drafts; recent searches |
Saving an edit (the draft is kept); drawings; Mermaid (shown as code) |
Waiting items: retry, discard, split |
Claude’s summary and related pages, the pager and swipes |
Search#
Search matches titles, tags, summaries and text as you type, each word as the start of a word
(vsc finds vscode); a title or tag match ranks first. Results show a snippet unless it only
repeats the title. Search needs the server.
Pinned. Tap the empty box and Pinned is listed first, above Recent: your pinned searches and pages, in the order of the page called Pinned (slug
pinned), a Markdown list of links. Pin a search with the pin button in the results header or on a Recent row; pin a page with Share, Pin to Search (a browser with no Share menu has no such row; add a- [Name](/p/slug)line to the Pinned page instead). The toast says Pinned, with Undo. Edit pins opens the page: rename a pin by its link text, reorder or remove it by its line. A page keeps its slug when renamed, so a pin to it survives. Copy link in the header copies the search’s address. Claude does not read the Pinned page. Pins show here, not on the capture screen the app opens on. See Make your own lists: a contacts directory and, for a bookmarks page, Keep an index of your pages.New from a search. A search with a tag filter, or a single kind, shows New at the right end of the top bar. It opens the composer with those tags and that kind filled in (a Note when no kind is chosen); cancelling straight away keeps no draft, and the saved page joins the list.
Topics.
is:topicgroups by area as the Topics tab does (Group: Area, None, A–Z), and the Topics tab has a Refine in Search button (a filter icon) at the end of its count line that opens it here.Recent. Tap the empty box for your last ten searches, newest first, filters and all. Tap one to run it again, ✕ to forget it, or Clear all. A search is kept when run (Enter, opening a result, or a few seconds with results), only on this phone.
Kinds. A row under the box: All, Notes, Todos, Later, Boards, Docs. One tap writes
kind:into the query; a second tap (or All) shows every kind again.Clear. A ✕ at the end of the box goes back to every page, newest first.
Filters. The slider button’s icon turns amber while the panel is open, and a small amber dot on it says filters are on (it shows no count). Each active filter is a chip with a ✕ in a row of its own under the kinds (#home, past month, A–Z), above the results header with the count of pages found. The panel’s rows: Tags (type to see matching tags among the results, or tap a top tag; before any search, the tags most pages were given), When (1w, 1m, 1y, Dates…, and By created date once a range is on), and Arrange, one line saying the order and grouping (Last edited · None): tap it for Order (Best match/Last edited, Newest, Oldest, A–Z, and for todos Due soonest and Priority) and Group; it stays open or folded as you leave it. Each tap applies at once; a second tap removes it. Clear filters keeps the words.
Paging. 100 pages at first, Show more adds 100. The newest-first list stops at 500 pages and a search at 1000 results: narrow with filters or dates, or reverse the order (
-sort:created) to go further. That is the screen’s reach, not a storage limit.Address. The query is in the URL (
/search?q=...), so reloads and Back keep it. Old/recentlinks open Search (/recent?kind=todoopens Todos); an old/tagslink opens Search.
Search syntax#
The chips only write the query, which you can also type. Filters alone list their pages newest first; the full grammar is in HTTP API and CLI.
Syntax |
Finds |
|---|---|
|
Start-of-word match; leave out; whole words in order. A |
|
Pages of a kind |
|
A tag ( |
|
A todo’s or Later page’s status |
|
Pages Claude wrote over MCP; pages whose text Claude changed over MCP |
|
The page’s |
|
Claude’s topic pages |
|
Pages with an image file; with a drawing made from a photo |
|
What Claude took the photo for |
|
A todo’s due day |
|
A todo’s priority |
|
A Later page’s type |
|
What a Later page is for |
|
Group the results in sections. Group writes it; Later’s own is type, Todos’ is Now, topics’ ( |
|
Last edited; dates are UTC days, both ends included; a time ago is |
|
Order (A-Z; newest; last edited; soonest due; high first). |
How matching works:
Words are stemmed (English) and accents folded:
runningfindsrun,cafefindscafé;ßis not folded toss.Punctuation and
_split words:e-mailiseandmail(soemaildoes not find it), anddon'tisdonandt.A quoted phrase matches whole words side by side:
"hello wor"does not findhello world.Filter names and values are case-insensitive (
Kind:Todo).An unknown value gives no error: an unknown
kind:,is:,has:orimage:, or adue:,after:orbefore:that is not a date or time ago, finds nothing (after-, leaves out nothing).
Store health#
The health checks, one count each from GET /api/health, are listed on Settings (the cog),
Store health (Settings also has Trash, The trash, Export everything,
Export your pages, and Import Markdown…, Import your pages). A tap runs the check’s search; Back returns to the screen. Nothing is dismissed
there: a check that keeps finding a page you are happy with is the check to change.
Search |
Finds |
|---|---|
|
No page link either way ( |
|
No tags (topic pages and gc’s Orphaned assets pages aside) |
|
An open todo not edited (text, title, kind or tags) for 60 days, not due today or later |
|
A photo or file shared with another page (not in the trash, not an Orphaned assets page; a photo whose embed you deleted from a page no longer counts for it) |
|
A |
|
A page with a Markdown heading (any level, setext too; not in code, a quote or a list item) that reads “Open questions” in any case (emphasis and links in it are ignored, a trailing colon too) and whose section is open. The section runs to the next heading of the same or a higher level, so deeper sub-sections and their boxes are part of it. A section with task boxes is decided by them alone: any unticked |
|
A board’s write-up older than the board’s photos, text, title, kind or tags, or made with an older prompt, or linking a page in the trash or gone (restore the page and it clears). Lists only board write-ups, which wait for you |
Related pages and topic syntheses are not listed: the nightly and weekly passes refresh them on their own (Scheduled jobs).
Todos#
Status. Todos and Later pages are open, in progress, done or cancelled. The toolbar starts with Mark done (open or in progress) or Reopen (done or cancelled), then Edit, Share, Delete. List rows have a tick box on the right (tap, or Tab and Space); the row stays in place with a done chip. A cancelled todo is struck through with a dash in its box; its tick box marks it done. Ticking or changing kind does not move the page up the list. The app only toggles open and done: in progress and cancelled are set through the API (a page’s
status, see HTTP API and CLI) or by Claude over MCP (MCP server), and the app shows them.Due date and priority. A todo may have a due date and a priority (high, medium, low). A page shows them in its first chip, before the tags (3 Nov · high, or Add due date): tap it to open Edit at its Due line, open, where you pick a date (Today, Tomorrow, Next week, Pick a date… for any other day, No date clears) and a priority, then Save at the top (Cancel beside it). Rows show them; a todo due before today and not done says overdue in red (that one word, the only red on the page). They belong to todos only: a page made a note keeps them, hidden. Neither is a revision nor moves the page. Today is your phone’s day on rows and pages, the server’s for
due:searches.Task lists drive a todo. A note with task boxes (
- [ ]) becomes a todo when saved (done if every box is ticked), unless its kind was chosen: the server files it, and the composer and editor still show Note. To keep such a page a note, Edit and pick Note. Later, Board and Doc never change. Boxes in code do not count. Rows and pages say how many are ticked (2 of 5). Ticking the last box marks it done; while every box is ticked, unticking one or adding an unticked one reopens it. A todo marked done by hand stays done. Mark done ticks no box; a cancelled todo is left as it is.Putting one off. Move its due date (tap its due chip, then Tomorrow, Next week or Pick a date…, and Save). Every open todo shows in Now.
Refine. At the end of the Todos count line, Refine in Search (a filter icon) opens the same view in Search, with its query filled in, to narrow it further.
The Todos screen (
/todos) has a switch, an icon over each word:View
Shows
Now (clock, default)
Todos not done or cancelled, in groups: Overdue (oldest due first), Today (by priority), High priority (every other high-priority todo, dated or not: soonest due first, those with no date last), Open (the rest by priority, due date, last edited). A date is a promise, so a dated todo never ranks below an undated one of the same priority. Done and cancelled are folded at the end (Done; those among the 500 last edited)
Upcoming (calendar)
Due from tomorrow to a week away, soonest first
All
Every open todo and the done ones, last edited first
Each list sets its own order. The view is in the address (
/todos?view=upcoming; switching changes it in place). Rows tick and swipe as anywhere. To search todos use Todos in the search kind row (kind:todo) withdue:,priority:andsort:due.New todo. The + at the right of the top bar on the Todos screen opens the composer on Todo; after saving you return to the list.
Mark done from the list. Opened from the Todos screen, Mark done moves to the next todo (3 of 9) with Undo; on the last it stays and offers Back to list. Opened elsewhere it stays on the page.
The page view#
Text#
Page text is Markdown as GitHub writes it:
Headings (each a size smaller, under the title), bold, italic, strikethrough, nested lists, quotes, inline and fenced code, links, bare
https://addresses (www.alone stays text), tables (write|in a cell as\|) and footnotes ([^1]).Task boxes (
- [ ],- [x]): tap one on the page to tick or untick it; that item’s line is saved at once, no editor. If the page changed elsewhere meanwhile it is shown as it is now and the tap is not saved: tap again. Boxes do not tick offline or in Preview, nor in code.[[slug]],[[slug|label]]link to another page (in a table cell[[slug\|label]]).[[slug#heading]]links to that page too (it opens at the top, not at the heading). A link with no label shows the page’s title (as it is now, if the page was renamed), as does a page’s full address written bare (not as[words](address)); a label you typed stays, and a link to no page shows what you typed. In Preview a link added since the page was opened, or on a new page, shows its slug.A
```mermaidblock is drawn as a Mermaid diagram; offline, or with a mistake, it shows as code.Maths (LaTeX, drawn by KaTeX):
$x^2$inline,$$...$$for a displayed formula (on lines of its own, or in a line), a```mathblock, or an environment such as\begin{align} ... \end{align}on lines of its own. Money stays text: an opening$must not be followed by a space, a closing$must not follow a space nor be followed by a digit, so$5 and $10is not maths (write\$for a$that would be). A formula with a mistake shows its TeX. Share’s HTML file has each formula’s TeX, as typed.[top](#my-heading)scrolls to## My Heading(lower case, hyphens for spaces).HTML is shown as typed, never run. Images show only when they are this server’s own photos (another site’s image shows as a link named by its alt text, else its alt text). Links open only to
https,http,mailtoor pages here; others stay plain text. A full address of a page here (on the address you opened the app at or any inSCRAP_PUBLIC_URL) opens in the app like[[slug]].A line break stays a line break. A wide table scrolls sideways in its own box. On a Later page a link on a line of its own shows as a card (site, path in words, Open).
The text is always stored as typed, and Edit shows it so (except the photo tokens, below).
Print (or Save as PDF) from the browser gives the title, the text and Claude’s card’s text, without the app’s bars and buttons. When a new sheet is needed, it breaks between sections rather than inside one, so a sheet may end with some blank space.
Linking pages#
A slug is the last part of a page’s address (
[[shopping-list]]or[[shopping-list|the list]]for/p/shopping-list). Only lower-case letters, digits and hyphens make a link;[[Shopping list]]stays as typed.The slug is made at creation from the title (ASCII letters and digits, lower case, hyphens, at most 60 characters;
-2,-3if taken) and is never changed or reused, even when retitled or trashed, so links survive renames. A title with no ASCII letter or digit getspage,page-2…Backlinks (under Related) are pages whose text, outside code, links it with
[[slug]],[[slug|label]], a Markdown link to its address ([label](https://<your scrap>/p/slug),<https://<your scrap>/p/slug>, on anySCRAP_PUBLIC_URLaddress) or a path ([label](/p/slug)). A bare address shown as a link is not counted.![[slug]]is not an embed here and is not counted.
Public pages#
A page can be made public: anyone with its address can read it on the public site (such as
https://pages.gkcluster.org/p/<slug>), read only, while scrap itself stays private. The site is
static: scrap pushes each public page to it, and it can reach nothing of scrap’s
(43. Public pages: a static site scrap pushes to,
Public site). It needs publishing set up on the server; without it,
Make public and Settings’ Public pages are not shown.
Make public… is the last row of Share. It asks first, every time: who can read it, and what goes public with the page: its tags, Claude’s write-up and summary (on a topic, its synthesis, which draws on its source pages), its attached files by name, and its N drawings, downloadable when its photos have drawings. Links to private pages show their titles as plain text. Cancel has the focus. A page with drawings takes a moment longer: the app loads Excalidraw to draw each one for the site, where readers can open it in excalidraw.com or download it (Publish pages). Make public publishes the page from the view on screen: the dates line says Publishing…, then Published and when, and the toast Page is public has Copy link.
A public page has a Public chip first in its chip row (a globe), which opens it on the public site. Public · not published yet on the dates line means it was made public somewhere else (over MCP, by a revert or by Restore from the trash) and has not been published since. Nothing publishes it until you say yes: Refresh in Settings asks first. A save doesn’t.
Make private acts at once: the page is taken off the site, and the toast’s Undo publishes it again (without the question). If the site can’t be reached just then, the toast says it comes off shortly: scrap finishes it within minutes.
Saving a public page here (an edit, Mark done, a task box) publishes it again. Publishing needs the server: if it can’t be reached the page is saved but not published, with the toast Saved. Not published while offline: Refresh all public pages in Settings later. Publishes are never queued.
Links come alive. When a page is made public or private, the public pages that link to it are drawn again and published, so a title in plain text becomes a link, or a link plain text; the toast says how many (· 2 pages linking here updated). Deleting a page here does the same, so its title goes from the public pages that showed it. Done elsewhere (over MCP, or on another device), those pages keep the old link or title until Refresh.
What a public page holds: the page as it shows here, with its maths, tables and Claude’s summary
and write-up; tags as links to the site’s tag pages; Mermaid diagrams drawn as here, light or
dark by the reader’s theme; photos as 2048 pixel previews (no location), a photo’s drawing beside it (with Photo and Drawing tabs,
and Open in Excalidraw and Download links), and other attached files. A link to a public page is a link
(a bare [[slug]] shows its title); a link to a private page is its words (a bare one, the private
page’s title). Claude’s Related and Linked from keep only public pages. An address of scrap
itself, or of its /api/, in the text reads a private link. Under each photo or PDF that
has one, Claude’s reading is published folded, closed, as in the app, a photo’s read text
in a closed fold of its own inside it (the site’s search finds its words). Left out: revisions,
where a reading is up to, the app’s buttons, and your Guidance for Claude (the page’s and
each photo’s). An
image’s alt text is published as the page has it, file names included; an image Claude drew
has its label.
The site is only as current as the last publish: a change made elsewhere (over MCP, a new write-up) reaches it at the page’s next save here, or with Settings, Public pages, Refresh (Public pages in Settings).
Photos and files#
A photo capture’s photos are in the page’s text, one line each (
), in the order taken. Edit shows each own photo or file as a token (⟦image 1⟧,⟦file 2: scan.pdf⟧) with a chip under the text box (thumbnail and name): tap a chip to select its token (to cut and paste it elsewhere) or ✕ to remove it. Saving restores the Markdown each token stands for; a token you delete or change is no photo any more. Claude reading a photo never changes the text.The page shows each photo where its line is, as a server-made preview (HEIC works), with no caption. Tap for the viewer. A file the text links to on a line of its own is a row where its line is (one linked mid-sentence is a link there, and a row below the text too); a file the text does not show is a row below the text: the name: tap to open the file, and save it from there, as a photo is saved from its viewer. Under a PDF’s or text file’s row, as under a photo: while it waits or is being read, where that is up to (Retry if reading failed); once read, one closed fold, Claude’s reading, with its page count and whether search finds it, for a PDF the text read from it, and Regenerate to read it again. scrap reads the text itself (no model), so search finds it. A page made from a PDF or text file then gets a title, tags and a summary from Claude, which is sent the start of that text (not with
SCRAP_FILE_ENRICH=ask: then press Ask Claude). A scan, a password-protected or unreadable PDF, or one over 30 MB is kept and listed but not searchable: its card says Text couldn’t be read; not searchable.Viewer. Full screen, file name top left, fitted to the screen. Pinch or double-tap to zoom, drag to pan, swipe (or arrows) for other photos. Fit, 100%, Download and Share are along the bottom; the cross at top right, or Back, closes. Share asks Link (the photo’s page) or Photo (the original if JPEG, PNG, GIF or WebP up to 20 MB, otherwise a JPEG up to 2048 px from the server; an SVG diagram is drawn as a PNG on a white background, 1024 to 2048 px on its longest side, since many apps can’t take an SVG, or sent as the
.svgif the browser can’t draw it). If a big photo takes too long to download, Photo ready offers Share again.
Editing and deleting#
Edit is a full-screen editor like the composer: Cancel, Preview and Save stay at the top, clear of the keyboard. Tags and a todo’s due date are folded into one line each. It opens the text where you were reading the page (at its top if you had not scrolled past the title).
Camera, Gallery and File under the text work as the composer’s: each photo or file picked is a token where the cursor is (
⟦image 3⟧,⟦file 2: minutes.pdf⟧), a paragraph of its own, with a chip under the text box; type between them freely, and ✕ on a chip takes its token out. Nothing is uploaded until Save, which sends the files with the edit (photos made smaller first, as the composer’s are): the server puts each one’s embed or link where its token is, all in one revision, and Claude reads the photos as any capture’s. A file whose token you deleted is not sent. Saving needs the network (offline, Save says so and keeps everything, the draft too, to try again); if the page changed elsewhere meanwhile, Save shows the conflict and nothing is uploaded. File takes PDFs, text and Markdown files. A topic page has no Camera, Gallery or File. They never change the page’s kind, title or tags. On a computer Add files (photos and files in one pick) takes the three’s place. The three stay on one line; on a narrow phone at a large text size each has its icon over its word.Lists. Enter on a bullet (
-), numbered (1.) or task (- [ ]) line starts the next item; Enter on an empty item ends the list. Above the keyboard are seven keys: five act on the line or selection, Task, Bullet, Number (each a plain line again if all are already), Indent and Outdent (renumbering numbered items). In a code block only Indent and Outdent work (two spaces). The sixth is Link (below), the seventh Undo (below). On a phone the keys show their icons alone (their words from about 413 px wide, more at a larger text size).Link. The Link key (a chain) puts
[[]]at the caret and opens a page picker: a search box over the pages changed most recently, and as you type, the pages that match. Tap one (or, once you’ve typed, press Enter for the first) and its slug goes between the brackets,[[slug]], with the caret after it; the link shows the page’s title. With words selected, they become the link’s words,[[slug|words]](in a table’s cell,[[slug\|words]]). Back, a tap outside the picker or its ✕ closes it with nothing picked: the[[]]stays with the caret inside, to type a slug, delete, or press Link again (a selection stays as it was). Undo takes back the page picked, then the brackets. Not in a code block.Undo. The last key, Undo, takes back the last change to the text (typing or one of the keys), and again for the one before, in order (most Android keyboards have no undo of their own). It is greyed out until there is something to take back. It stops when the text is back to how it opened, or to how it was when you last typed in a tag or the title or added a photo or file, even if you deleted and retyped before that point. For older text use Cancel, or restore a saved revision. There is no Redo. Ctrl+Z does the same on a computer.
Drafts. The composer and Edit keep a draft on the phone as you type (tags and photos too), returning after Back, a reload or a closed app: the composer’s as the draft row, an edit as Edit draft on its page (Discard it drops either). Back closes the editor, not the app. Cancel asks before discarding changes.
Conflicts. If the page changed elsewhere since you opened it, the other version is shown under yours: merge into your text and save (overwrites theirs), or discard yours.
Revisions. Every save that changes a page’s title or text is kept as a revision, whether by you or Claude over MCP. The app has no screen to browse or restore them; Claude can use the MCP
revisionsandreverttools (MCP server). Tag and kind changes are kept apart. Download and the export (Export your pages) hold only the current text.Size. A page’s text holds at most 1 MB (about a million letters; photo links a capture adds count). Save says so before sending and keeps the text for you to shorten or split. A page imported over the limit can still be edited if its text stays as it is or shrinks.
Removing a photo. Deleting a photo’s line (token) removes it from the page. Save warns first, naming each photo whose line went: no longer shown on any page or still shown on N other pages. Cancel returns to the editor; OK saves. The file and what Claude made of it are kept: once no page shows it, the nightly
scrap gcgathers it onto the month’s Orphaned assets todo page, where you cut it into another page to keep it or delete the page (see Deleting unused files (scrap gc)). Once that page is purged from the trash, the files it gathered that no page shows go with it.Delete moves the page to the trash with Undo for a few seconds and goes back one step (to the list or page you came from; a page opened cold goes where the top bar’s Back names). After Undo is gone, the page waits in the Trash (below) for 30 days, then is deleted for good.
The trash#
A deleted page goes to the trash, and nothing deletes it until it has been there for 30 days
(the server’s SCRAP_TRASH_DAYS, set as the nightly scrap gc is: see
Scheduled jobs). Then the nightly scrap gc purges it for good, with its
history, Claude’s results and the photos and files no other page uses. A purged page still
exists in the backups until they are pruned.
To see what is in the trash or bring a page back:
Open Settings (the cog) and tap Trash.
Each deleted page shows when it was deleted and when it goes for good (Deleted 3 Oct · deleted for good after 2 Nov; at the next nightly clean-up once the date has passed). The newest 100 are listed; a line under them says how many there are in all.
Tap Restore on a page to bring it back as it was. The toast’s Open opens it.
A topic page in the trash is never purged: it stays there so that Claude does not make the topic again (kept: Claude won’t make this topic again). Restore it to have the topic back.
With purging off (SCRAP_TRASH_DAYS=off, or a chart whose gc does not delete), pages stay in the
trash until restored, and no date is shown. Over MCP, the trash tool lists the same pages with
their purge_at, and undelete brings one back (MCP server).
What Claude made of a photo#
Claude reads photos only with ingest on. It is off by default: set SCRAP_INGEST=on and give
scrap an ANTHROPIC_API_KEY (the Helm chart’s ingest.enabled with an existing Secret holding
the key), as in Claude ingest and the deploy guides. Without ingest, photos are
stored and queued, and each shows Waiting: Claude ingest is off.
Everything Claude makes is in a brown card of its own, so it never reads as your text: Claude’s reading under each photo, and one About this page after the page’s text (summary, a board’s write-up, related pages). About this page’s action is at the end of its title line: Generate first, Regenerate after (Retry if it failed). Its parts are folds; the phone remembers which are open.
Under each photo:
State, only while Claude has it, as words: Waiting for Claude, Claude is reading it…, Failed with reason and Retry, or Paused with reason and Resume (nothing is lost). The page checks every 10 seconds while a photo waits.
Claude’s reading, one fold, always closed when the page opens: the description, the text read from the photo (selectable) in a closed fold of its own, Text read (mostly there for search), Guidance for Claude, and at its end Regenerate, which sends it again; if you edited its drawing it asks: Replace mine, or Keep mine (Claude’s new drawing goes into History). A photo never sent to Claude has Generate in the fold instead.
Guidance for Claude, at the bottom of a photo’s reading, is a note for Claude’s next reading of that photo (such as “it’s a wiring diagram; ‘R1’ is a resistor”): the same fold as About this page’s. Save only keeps it (the fold then says (set)); the next Regenerate (or Generate) sends it with the photo, and it steers the description, the text read and Claude’s drawing. A drawing you edited is still never replaced without asking. It is kept for later Regenerates, until you clear it and Save. It is that photo’s only: the page’s own guidance steers its write-up, and neither feeds the other. It is never published, nor in Share as HTML. Files (a PDF, a text file) and the images Claude drew have none.
An image Claude drew and added (
add_svg) has nothing under it: Claude doesn’t read its own images. Its label is its alt text, and what search finds.The drawing of a whiteboard or diagram, beside the photo on a wide screen or under a Photo / Drawing switch. It is a picture (scrolls with the page, no gestures) lined up with the photo.
The drawing viewer#
Tap the drawing to open it full screen.
Pinch and drag to zoom and pan; Fit resets. The ✕ (Close) at the top left closes it. Photo shows the photo beside (landscape) or over (upright) the drawing. Back closes.
Edit turns on Excalidraw’s tools (draw, shapes, arrows, text, eraser; not images). Undo is in the top bar, Cancel puts the drawing back, Save stores a new version.
Drafts. The edit is kept on the phone as you draw; after Back, a failed save, a reload or a closed app, Edit (with a dot and a line saying so) brings it back (Discard it drops it). If the drawing changed elsewhere meanwhile, saving says so: Save mine adds yours as a newer version (theirs stays in History), or Discard mine, show theirs.
More, History lists every version (Claude’s, yours, restored) with Restore, which makes an old version current again as a new copy. Nothing is overwritten.
More, Share sends the drawing as a PNG, where the phone can share files; elsewhere there is no Share, and Export PNG saves it.
More, Export saves
.excalidraw, PNG or SVG. The page’s Download zip includes it beside the photo, with the description and text in the Markdown.It follows the phone’s light or dark theme and needs the server.
Excalidraw is loaded only when a drawing is first shown (about 430 kB compressed; then cached by the browser), never for capture or search. Mermaid loads the same way, for a page with a diagram, and KaTeX for a page with maths (it is kept for offline use). Excalidraw’s fonts come from scrap itself, never a CDN; Chinese, Japanese and Korean handwriting falls back to a system font.
npm --prefix web run sizeprints current figures.
What Claude makes of a note or link#
With ingest on and SCRAP_ENRICH not off (on by default once SCRAP_INGEST=on; see
Claude ingest), a text or link page gets an About this page card under its
text. Without ingest the card is hidden unless the page already has a summary or related pages.
Regenerate at the end of the title line has Claude read the page again (a saved link is fetched again; a board’s write-up and related pages are written again). Status: Waiting for Claude (or Waiting: Claude is off), Claude is reading it…, Paused with Resume (the queue is shared with photos), Claude couldn’t read it with Retry. Before the first read the card says what it would do, with Generate.
Summary is Claude’s one-line summary, which search also covers (on a board, the lead of the write-up).
Related has pages Claude picked, plus pages that link here (· links here) and topics whose synthesis cites the page (· in a topic). Each is a line of its own, with the icon of its kind before the title.
Topics: on a page that is a source of topic pages, a fold names them, a line per topic with the topic icon, each a link.
Summary, Topics and Related are open until closed. Tags Claude added have a ✦ before their name.
Reading needs the server; the page rechecks while Claude has it queued or is reading it.
A board’s write-up#
A page with a whiteboard or diagram photo (not a to-do sticky, which becomes a checklist) gets a write-up in About this page as the fold In detail (closed until opened). Its lead is the card’s Summary. It is Claude’s, never part of the text you edit.
Provenance. Model, date and version (and restored from version N).
Content. A one or two sentence lead, then overview, structure, decisions, open questions and related pages as fits. Links in it open those pages and show in Related (· in a write-up) on both pages. Search finds its words.
Regenerate (Generate before there is one) writes it again and finds related pages afresh. A new board photo, or a Regenerate of a board photo, writes it again anyway. While working the card says Waiting for Claude or Claude is writing it….
Stale line. The page, a page it links to, or Claude’s prompt has changed since this write-up. appears once you edit the text, title or tags, Claude’s prompt changed, or a page it links is deleted. Nothing rewrites on its own;
is:outdatedlists such pages.Guidance for Claude is a note for the next write-up (such as “focus on the data flow; ‘Enc’ means encoder”). Save keeps it without writing; Regenerate uses it and the new write-up keeps a copy. Regenerate replaces the old write-up: only the current one is kept.
The first write-up comes once Claude has read all of the board’s photos; related pages follow.
Boards read before the feature get theirs from scrap enrich --writeups (HTTP API and CLI).
Claude’s topic pages#
Each week (with Claude on; see Topic pages) Claude looks across your pages for things they keep coming back to, such as a tool or a family of systems, and makes a topic page for each, citing its sources. They are Claude’s, kept apart from your pages.
The Topics tab (
/topics) lists them grouped by area (the top-level tag most common among a topic’s sources, biggest group first; Other if the sources have no tags). Each row has title, one-line summary, source count and last change. A dot marks a topic new or changed since you last opened the tab or that topic on this device (kept on the phone; none the first time). The filter box narrows by title, summary and area. With no topics, the tab says what they are and that they come from the weekly pass.A topic page says Topic by Claude under its title (tap for the Topics tab). Its text is yours: notes on the topic, empty at first, edited like any page. Claude never writes there. Claude’s synthesis is in About this page: lead as Summary, the rest as In detail (open until closed): overview, key facts linking to their pages, how sources relate, contradictions and open questions. Regenerate, Guidance for Claude and Versions work as for a board’s write-up.
Sources in the same card lists the pages the topic is made from (title link and kind). ✕ takes a page out: the next synthesis leaves it out and the weekly pass never re-adds it. It stays listed, struck through and marked taken out, with Put back (the toast’s Undo does the same). Either way the synthesis is written again. Needs the server. The topic’s Related leaves out its sources.
Search puts a matching topic page first within its tier (every word in the title; or in title, tags or summary), with a Topic label and an open-book icon;
is:topiclists them all.To get rid of a topic, delete it: it goes to the trash and the weekly pass does not re-make it. It stays in the trash (never purged) as the record that you did not want it; restore it from the Trash to have it back (The trash).
iPhone and iPad#
Safari can add scrap to the home screen; capture, view, search and the offline queue work, with these limits:
No share target. scrap is not in the iOS share sheet. A Shortcut posting to
POST /api/captures(HTTP API and CLI) can stand in.Storage eviction. iOS may evict a home-screen app’s storage after weeks of non-use, dropping anything queued; open it once a connection is back.
Text size. With Settings → Text size → Phone, the app follows the iPhone’s Text Size and Larger Text, keeping its own font and line spacing and never going below its usual 17 px. At the largest sizes kind rows wrap and the nav grows; a page’s toolbar may wrap Delete onto a second row.
Updates#
The app’s files are cached so it opens instantly and offline; API answers and files never are. Some parts load only when first used: the Todos screen, a page’s pager and swipes, Claude’s summary and related pages, what Claude made of a photo, Mermaid and the drawing viewer.
After a deploy the app shows Update available, Reload. Until you tap it the old version keeps
running; it is replaced anyway the next time the app opens after being closed. The app checks for
a new build each time it comes to the front. The build id at the bottom of the search screen
(scrap- and the build) says which build a phone runs.
Tapping that build id five times turns on a small debug panel of viewport numbers (five more turn it off); it helps report how a phone’s keyboard resizes the page.
Build it (developers)#
The source is in web/ (TypeScript, Vite, no UI framework; the drawing viewer in web/src/board/
uses Excalidraw and React in a chunk of its own). The container image builds it; for
scrap serve from a checkout, build once with Node 22:
npm --prefix web ci
npm --prefix web run build # writes src/scrap/static/, which scrap serve picks up
Without it scrap serve still serves the API, and / says how to build the app. To try a build
from another folder without restarting, point SCRAP_WEB_DIR at it. To work on the app, run
scrap serve (127.0.0.1:8000 by default) and npm --prefix web run dev (Vite proxies /api and
/share to port 8000).
Command |
Does |
|---|---|
|
Biome and TypeScript |
|
Unit tests |
|
After a build: the app shell stays within 16.5 kB gzipped and never loads Excalidraw |
|
Playwright against a real |
|
Regenerates the app’s icons |
Licences of what it bundles#
Served by scrap itself, never from a CDN:
Page text: markdown-it and its footnote plugin (MIT); diagrams: Mermaid (MIT); maths: KaTeX and its fonts (MIT).
Drawing viewer: Excalidraw (MIT). Its fonts are served as they come from the package, without licence texts: Excalifont, Virgil, Nunito, Lilita One, Cascadia Code and Liberation Sans (SIL OFL 1.1), Comic Shanns (MIT) and the interface font Assistant (OFL 1.1). The 14 MB CJK font (Xiaolai) is left out. The licences are in the fonts’ own projects (OFL: https://openfontlicense.org).