Diese Anleitung auf Deutsch lesen
A local-first, browser-based document archive: capture, OCR, tag, and browse your own documents — no server, no account, no upload. Everything lives in one SQLite database and a folder of files on your own disk, opened and written directly by the browser.
Most document-management tools want your files in their cloud. Dossiary is the opposite: it's a single HTML file that reads and writes a folder you choose, using the browser's File System Access API and sql.js (SQLite compiled to WebAssembly) — nothing is ever uploaded anywhere. Point it at a folder, and that folder is your archive.
It also has no native-code dependency of any kind, so it runs identically on Apple Silicon or Intel, macOS/Windows/Linux — anywhere a modern Chromium browser runs — indefinitely, with no risk of the "Intel-only app stops working" problem that motivated this project in the first place.
- Recent libraries — the last 5 libraries you've opened show up on the
startup screen; click one to reopen it with a single permission-confirm
click, no need to browse to the folder again. This works by storing the
folder's access handle in your browser's IndexedDB and re-requesting
permission on it, not by uploading or copying anything — the data itself
is never touched until you click. Remove an entry with its ✕ (e.g. on a
shared computer, or a library you're done with) — there's no separate
setting to turn this off; removing is the opt-out. Note that on
file://pages, IndexedDB storage is shared across every local file you open in the browser, so in principle any other local HTML page you open could read a stored library's access handle too — though it would still need you to click Allow on a permission prompt naming the real folder. - Browse — sortable, searchable, filterable list of every document in the library, with category/type filters. Search matches title, category, subcategory, document type, notes, OCR text, tags, people, and every custom field's value.
- Capture — add a new document (PDF or image), with client-side OCR via
Tesseract.js running entirely in
your browser. Language options: German, English, or both auto-detected
together, plus single-language French, Spanish, Chinese (Simplified), and
Chinese (Traditional / Cantonese — Tesseract has no separate Cantonese
model, since Cantonese text is written with the same traditional-character
script). For JPEG/PNG images, this also builds a searchable PDF — the
image with an invisible, selectable text layer positioned over each
recognized word (the same "sandwich" technique tools like
ocrmypdfuse) — while the original image is preserved untouched in a subfolder next to it, mirroring how Mariner Paperless itself laid out processed vs. original files. If you're starting from a paper document, a "Need to scan a paper document first?" toggle in the capture form explains how to scan it first — Image Capture or Preview on macOS, the Windows Scan app on Windows, detected automatically — since a browser has no way to drive scanner hardware directly — see Limitations below. - Inbox — a lightweight amber banner appears on opening a library if its
inbox/folder (at the library root, alongsidelibrary.sqliteandfiles/, and created automatically the same wayfiles/is — no manual setup needed before dropping a file in by hand or pointingscan_watch.pyat it) has any files waiting in it. Click "Review" to see them and add each with default values (just the file, plus a filename-derived title) — the rest of the metadata is left blank for you to fill in from the document's own Edit dialog afterward. This pairs with the standalonescan_watch.pyscript below, which moves finished scans from wherever your scan software saves them into thatinbox/folder — Dossiary itself never watches the filesystem or writes a document automatically; adding one from the inbox always requires this explicit click. - Review queue — a second stage after the Inbox: every document added from the inbox (category, type, and date all still blank at that point) is automatically flagged "needs review" and shown in its own section above the main table, instead of sorting to a possibly-unnoticed spot in it. Click "Edit" on a queued document to fill in its metadata, or click the row to open it. Only the explicit "Done" button — on the queue row itself, or in the document's own detail view — clears the flag and moves it into the main table; saving an intermediate edit does not, so you can save your progress partway through without losing your place in the queue. Any document can be flagged this way, not just inbox imports — open a document and click "Flag for review" if you want to come back to it later. Flagging and archiving are independent: flagging an archived document doesn't unarchive it, and vice versa — an archived document stays reachable only via "Show archived" in the main table, same as any other archived document.
- Waste bin — "Delete" on a document doesn't actually destroy anything;
it just moves the document to the "🗑 Waste bin" (from the toolbar),
where it stays until you click "Restore" — there is no "empty bin"
action anywhere, so nothing you delete is ever truly gone. A deleted
document is hidden everywhere else, including the main table with "Show
archived" checked and the review queue, and its detail view only offers
Restore until you do. Files on disk, thumbnails, and the sidecar
.txtare never touched either way. - Reports — a 4th nav view totals your documents by Category, Type, People, or any custom field, grouped by currency so amounts in different currencies are never added together, with a date-range filter and a print-friendly layout for tax season or expense reimbursement.
- Collections — organize documents into your own named groupings, reachable from an expandable Collections section in the nav. Manual collections are hand-picked lists (select documents in the table or add them from a document's own detail view); Smart Collections save your current search/category/type/person/field filters as a live view that keeps matching new documents automatically.
- Spotlight/Finder search — every captured document also gets a plain
.txtsidecar file (title, category, tags, notes, OCR text, custom field values) written next to it, so macOS's built-in file search can find documents by fields that otherwise only live insidelibrary.sqlite. This isn't a real Spotlight integration (not possible from a browser — see Limitations below); it's just an ordinary text file that happens to get indexed like any other. - Custom fields, fully generic — text, number, date, checkbox, and person fields (Organization, Year, Date From, Paid, Reimbursable, Author, Collaborator — whatever your library uses) are all modeled the same way, backfilled from Mariner's own field definitions and values. Not a fixed set of hardcoded fields. A person-type field works like People itself (see "Tag & organize" below): comma-separated, a document can relate to more than one person through it, and every person-type field shares the same underlying list of names, so someone typed into Author autocompletes and searches the same as one typed into People.
- Tag & organize — category, subcategory, document type, payment method, amount, date, notes, people, custom fields, and free-form tags per document — a document can relate to more than one person, filterable the same way tags are
- Open originals — one click to open the actual file from disk
- File paths shown in the detail view — a
Fileline (andOriginal, for a captured image that got turned into a searchable PDF) showing the path relative to your library folder, so you can find it yourself in Finder (macOS), File Explorer (Windows), or your file manager (Linux). Browsers have no API to reveal a file in the OS's file manager directly or expose its absolute path, so this is as close as the app can get. - Edit — click any document, then "Edit" to update its metadata (title,
category, subcategory, type, payment method, amount, date, people, tags,
custom field values, notes, OCR text) after the fact. This only ever
changes
library.sqlite— the underlying file on disk is never touched or replaced. - Configurable columns & filters — the "⚙ Columns" button in the
toolbar lets you show/hide table columns (Category, Type, Payment method,
People, Date, Imported, Amount, Tags); each one that supports filtering
shows or hides its matching filter dropdown at the same time. The choice
is saved in
library.sqliteitself, so it travels with the library folder rather than being tied to one browser or device. (Custom fields as table columns/filters is planned but not built yet — see Limitations.) - The table header stays visible while scrolling — useful once a library has enough documents that the list genuinely scrolls. The document list itself is a bounded, independently-scrolling area (not the whole page), so column headers (and the ability to click one to sort) are always in reach no matter how far down the list you are.
- Document previews — every document can show a small preview image in
its detail view. Migrated documents get Mariner's own thumbnail, copied
over directly by
migrate_to_new_library.py. Newly captured documents get one generated automatically (an image gets downscaled directly; a PDF gets its first page rendered via pdf.js). A "Generate preview" / "Regenerate preview" button in the detail view lets you create one on demand for any document that's missing one, or refresh an existing one. - Dynamic fields per document type — capture/edit forms only show the custom fields (and People) actually configured for whatever document type you've picked, in the configured order, mirroring how Mariner Paperless itself decided which fields to display per type. An unconfigured document type (a brand new type, or a library where this wasn't tracked) shows none of its custom fields at all — matching Mariner's own behavior, where fields have to be explicitly assigned to a type before they show up.
- Date defaults to today when capturing — since that's right for a freshly-received document but wrong for a backlog of older mail, it's visually flagged (amber-tinted, with a "double-check this" note) until you actually touch the field, so an unreviewed guess doesn't quietly pass for a real value.
- Clear button on every datalist field, plus Amount and Currency — a small "✕" on Category, Subcategory, Document Type, Payment method, People, Tags, Amount, and Currency, in both capture and edit forms, clears that one field and refocuses it — for the datalist-backed fields, this pops the full list of existing values back up instead of staying filtered to whatever was typed before, handy when you want to pick a different value from the list rather than retype one.
- Re-run OCR on an existing document — the Edit dialog has its own "Run OCR" button, refreshing just the OCR text field against the document's actual saved file. Unlike the capture form (images only), this works on PDFs too — the majority of saved documents — by rendering the first page to an image first.
- Document Type is placed prominently, near the top of both forms — since it's the one field that determines whether Organization, People, or any custom fields show up at all (see "Dynamic fields per document type"), it's deliberately not just another field in the middle of the form. Pick it first, then everything below reflects that choice.
- Field settings — the "⚙ Manage fields" button opens a dialog for managing which fields show per document type (and in what order), plus a default document type and a default currency that pre-fill the Add Document form (see "Amount has a linked Currency field" below), plus two per-field checkboxes — Column and Autocomplete (see below) — available for any real custom field. Mirrors Mariner Paperless's own Document Types / Fields / Display Fields screen: pick a type on the left, add fields to it from the middle column, reorder or remove them on the right — changes save immediately. Deliberately scoped to document types already in use (a brand new type comes into existence by typing it into the Add/Edit form, not from this dialog), and to toggling/reordering existing custom fields — it doesn't create new ones from scratch (see below for where that happens instead). Payment method is a completely ordinary custom field — despite being a mandatory, always-present field in Mariner itself, there's no reason for a general-purpose tool to keep it as a hardcoded special case, so it's just one more row in the Fields list: toggleable per document type, reorderable, and (see below) column/filter/autocomplete-able exactly like anything else. Amount keeps a small, deliberate exception — see "Amount has a linked Currency field." Reclassifying a document to a type where a field isn't configured never discards the value already saved — it's just not shown until you either add the field back for that type or reclassify again. The detail view's header reflects this too: Payment and Amount only appear there when a document actually has a value for them, rather than always showing an empty placeholder.
- Any single-valued custom field can become a table column, a filter, and offer autocomplete — two checkboxes next to each field in Field Settings' Fields list (not offered for person-type fields like People, Author, or Collaborator — see Limitations). Column adds a sortable table column (click its header to sort, numerically for Number-type fields) and, for Text/Checkbox fields, a toolbar filter dropdown built from the real distinct values already in your library — Number/Date fields get the column without a filter dropdown, the same way the built-in Date and Amount columns already work, since a dropdown listing every distinct number or date isn't useful. Autocomplete (Text fields only) offers previously-used values while typing — the same underlying mechanism Payment method itself now uses. Both start off for a newly created field, so a fresh custom field doesn't clutter the table or toolbar until you decide it's worth surfacing there.
- Add a custom field right from the capture/edit forms — a "+ Add a custom field" toggle below the custom fields, hidden until you've entered a document type (a field always has to attach to some type). Pick a name and a type (Text/Number/Date/Checkbox/Person — no Currency option; for a monetary value use the built-in Amount field instead, which the form reminds you of), and it's created and immediately shown on the document you're filling out — no trip to Field Settings required, and no document type needed there in advance either, which matters for a library that's never had a custom field at all (nothing pre-migrated from Mariner, and nothing created yet). Adding a field this way never disturbs anything already typed into the document's other fields — a real risk that was deliberately designed around, not just tested for; a naive implementation that simply re-rendered the whole custom-fields area would have silently discarded whatever was already filled in. A name that's already in use is rejected rather than silently attached to the current type or duplicated — use Field Settings (which already lists every existing field) for that instead.
- Amount has a linked Currency field — both are ordinary custom fields under the hood now (their capture/edit form inputs are two normal, independently-positioned fields, each with its own clear button), but they deliberately keep one exception from the fully generic system above: neither gets Column/Autocomplete checkboxes in Field Settings. Currency is free text, with its own autocomplete from currencies already used in the library, rather than a fixed dropdown (real documents mix symbols like "€"/"$" and codes like "EUR"/"USD"). Their table column and detail-view line always stay combined into one "123.45 EUR" display (amount, then currency, consistently) rather than becoming two separate columns, since free text makes it impossible to know whether a given value is meant as a prefix symbol or a suffix code. Sorting the Amount column sorts by the raw number only — there's no currency conversion, since this is a personal document archive, not an accounting tool. A default currency, set once in Field Settings, is optional and unset by default — when configured, it pre-fills new captures' Currency field the same way the Date field pre-fills to today: visually flagged as a guess (amber, with a "double-check this" hint) until you actually touch the field. It's a per-library setting, not a hardcoded assumption, since Dossiary is a general-purpose, single-file, downloadable tool — a fixed default would just be silently wrong for anyone whose library isn't in that one currency. Editing guesses too, but only for a document that already has a real amount and no currency saved — e.g. one captured before a default currency was ever set — so that gap can actually be closed rather than requiring the Currency to be retyped from scratch. Any other blank Currency in Edit (no amount, or an amount of zero) is left alone: that's the document's real, saved state, not something to guess at.
- Editing never hides data behind a configuration change — if a document has a value in a field that isn't (or is no longer) configured to display for its current type — reclassified, or the field got removed from that type's setup in Field Settings — the Edit dialog still shows it, appended after the normally-configured fields and visually marked ("Not shown for this document type"), so you always have the chance to review, fix, or clear it. It just won't appear again once cleared, or once you change the document's type to something that doesn't include it and don't touch it.
- Open
dossiary.htmldirectly in Chrome or Edge (double-click it, or drag it into a browser window — don't use an embedded preview pane; folder write access requires a real top-level page). - Click "Open library folder" and choose a folder. If it's empty,
you'll be offered to initialize a new library there. If it already has a
library.sqlite(e.g. from a migration — see below), it opens straight into your existing documents. - Click "+ Add document" to capture something new.
Dossiary is still just the one file — there's no installer and nothing to build — but Chrome and Edge can both turn a page you already have open into something that looks and launches like a native app, with its own icon and window (no tabs or address bar). This is optional; opening the file directly in a browser tab works exactly the same.
macOS (Chrome): open dossiary.html in Chrome, then ⋮ → Save and
Share → Create Shortcut… (older Chrome: More Tools → Create
Shortcut…). Rename it to "Dossiary", check "Open as window", and click
Create. It's added to ~/Applications/Chrome Apps.localized/ and shows
up in Launchpad and Spotlight like any other app.
Windows (Edge): open dossiary.html in Edge, then ··· → Apps →
Install this site as an app. Rename it to "Dossiary" and click
Install. Edge adds a Start Menu entry and offers to pin it to the
taskbar or desktop.
Either way it's the same file underneath, so nothing about how it stores or
reads your library changes — the browser is just giving it its own launcher
and window instead of opening as a tab. (Since dossiary.html has no
<title> tag, both browsers will suggest "dossiary.html" as the app name by
default — that's the one thing worth renaming during setup.)
If you're migrating from the discontinued Mariner Paperless app, see MIGRATION.md for the conversion tools and steps.
A small standalone Python script (stdlib only — no pip install needed) that
watches a folder your scan software saves finished scans into (e.g. ScanSnap
Home's own "save to folder" destination) and moves each stabilized file into
a Dossiary library's inbox/ folder, for the in-app Inbox feature
described above to pick up:
python3 scan_watch.py --drop-folder ~/Scans --library ~/Documents/MyLibrary
It runs continuously by default (checking every --poll-interval seconds,
default 2), or once with --once. A file is only moved once it hasn't been
modified for --settle-seconds (default 2), so a scan still being written
isn't grabbed mid-write.
This is deliberately filesystem-only — it never touches library.sqlite
itself, doesn't assign document IDs, and doesn't set any metadata. Dossiary
is the library's sole writer to library.sqlite (it loads the whole
database into memory in the browser tab and only writes it back out on an
explicit save), so a second process inserting rows directly could silently
lose work to whichever side saved last. Keeping this script to "just move
the file" sidesteps that risk entirely, and means nothing is ever added to
your archive without an explicit click inside the app itself, in keeping
with Dossiary's own "no silent writes" design (documents are only
ever written from something you clicked, not from data arriving on disk on
its own).
erDiagram
documents }o--o{ tags : "tagged (document_tags)"
documents }o--o{ people : "involves (document_people, vestigial)"
documents }o--o{ fields : "custom field values (document_field_values)"
documents }o--o{ fields : "person-field values (document_field_people)"
documents }o--o{ people : "person-field values (document_field_people)"
fields ||--o{ document_type_fields : "field_name matches fields.name (by name, not FK)"
documents {
int id PK
string title
string category
string subcategory
string document_type
string payment_method
float amount
string currency
string date
string import_date
string notes
string ocr_text
string ocr_language
string file_path
string original_file_path
string created_at
string source
int source_legacy_id
string thumbnail_path
int archived
int needs_review
int deleted
int searchable_pdf_built
}
tags {
int id PK
string name
}
people {
int id PK
string name
}
fields {
int id PK
string name
string type
int show_as_column
int autocomplete
}
document_type_fields {
string document_type
string field_name
int position
}
document_type_fields.document_type and documents.document_type match by
plain-text name, not a SQLite foreign key — like category/subcategory/
payment_method on documents itself, this schema stores resolved names
directly as TEXT rather than keeping separate lookup tables, so there's
nothing left to declare a real foreign key against. document_field_people
is genuinely a three-way relationship (document × field × person), shown
above as two separate binary lines for legibility rather than as its own
box. document_people is vestigial — see below. settings isn't shown
here since it's a plain key-value table with no relationships to anything
else; see its own description below.
The diagram above is a relational overview; the full column-by-column listing (types, defaults, and the reasoning behind vestigial/nullable columns) follows:
documents
id INTEGER PRIMARY KEY
title TEXT
category TEXT
subcategory TEXT -- independent of category, NOT a child of it (see note below)
document_type TEXT
payment_method TEXT -- VESTIGIAL -- see "fields"/"document_field_values"
amount REAL -- below. Neither read nor written anymore; kept
currency TEXT -- (never dropped) so old bytes aren't destroyed.
date TEXT -- ISO 8601, the document's own date (e.g. invoice date)
import_date TEXT -- ISO 8601, when the document was scanned/captured/imported
-- (for migrated documents, this comes from Mariner's own
-- import date; for captured documents, it equals created_at)
notes TEXT
ocr_text TEXT
ocr_language TEXT -- 'deu' / 'eng' / 'eng+deu' / NULL
file_path TEXT -- relative to library root, e.g. "files/3_invoice.pdf"
original_file_path TEXT -- relative to library root; now set for every new
-- document (Inbox or capture), not just searchable PDFs
searchable_pdf_built INTEGER -- 0/1, default 0; whether Dossiary's own OCR+jsPDF
-- pipeline built the file currently at file_path --
-- original_file_path's presence alone no longer means this
created_at TEXT -- ISO 8601, when the record was created
source TEXT -- 'migrated', 'captured', or 'scan-inbox'
source_legacy_id INTEGER -- traceability only, for migrated documents
thumbnail_path TEXT -- relative to library root, nullable
archived INTEGER -- 0/1, default 0; reversible "no longer needed" flag,
-- hidden from the default view -- see Features above
needs_review INTEGER -- 0/1, default 0; "not yet reviewed" flag, shown in the
-- review queue instead of the main table -- see Features above
deleted INTEGER -- 0/1, default 0; soft-delete flag, reachable only from the
-- Waste bin -- see Features above. No file/thumbnail/sidecar
-- is ever touched, and there's no "empty bin" purge feature.
tags
id INTEGER PRIMARY KEY
name TEXT UNIQUE
document_tags
document_id INTEGER
tag_id INTEGER
PRIMARY KEY (document_id, tag_id)
people
id INTEGER PRIMARY KEY
name TEXT UNIQUE
document_people
document_id INTEGER -- VESTIGIAL -- see "fields"/"document_field_people" below.
person_id INTEGER -- Neither read nor written anymore; kept (never dropped)
PRIMARY KEY (document_id, person_id) -- so old bytes aren't destroyed.
settings
key TEXT PRIMARY KEY
value TEXT
fields
id INTEGER PRIMARY KEY
name TEXT UNIQUE
type TEXT -- 'text', 'number', 'date', 'checkbox', or 'person'
show_as_column INTEGER -- 0/1; adds a sortable table column, and (text/
-- checkbox types only) a toolbar filter dropdown.
-- Not offered for 'person'-type fields (see below).
autocomplete INTEGER -- 0/1; text-type fields only -- offers previously-
-- used values while typing
document_field_values
document_id INTEGER
field_id INTEGER
value TEXT -- always stored as text; interpreted per fields.type when read.
PRIMARY KEY (document_id, field_id) -- Not used for 'person'-type fields -- see below.
document_field_people
document_id INTEGER
field_id INTEGER -- a `fields` row of type 'person' -- People, Author, Collaborator, ...
person_id INTEGER
PRIMARY KEY (document_id, field_id, person_id)
document_type_fields
document_type TEXT
field_name TEXT -- a name from `fields` -- includes 'People' itself now, not
-- just custom fields (see below)
position INTEGER -- display order within this document type
PRIMARY KEY (document_type, field_name)
settings is a small key-value table for app preferences that should
travel with the library rather than live in browser storage — currently
visible_columns (a JSON array of which table columns and their
matching filters are shown), default_document_type (pre-fills the Add
Document form's Document Type field), and default_currency (pre-fills
new captures' Currency field as a dismissible guess — see Features above;
unset by default, since this is a general-purpose tool with no currency
that's correct to assume for everyone).
Custom fields are fully generic (fields + document_field_values for
single-valued types, fields + document_field_people for person-type
fields) — Organization, Year, Date From, Paid, Payment method, Amount,
Currency, People, Author, Collaborator, whatever your library actually
uses. Each field has a type (text/number/date/checkbox/person)
that determines how it's rendered and how its value gets interpreted,
plus the show_as_column/autocomplete capability flags described above
(not offered for person-type fields — a multi-valued field doesn't fit
a single table cell or a useful filter dropdown the way a single-valued
one does). Populated by migrate_to_new_library.py from Mariner's own
field definitions and real values for migrated libraries, and by two
one-time, idempotent migrations run on every library open:
migrateSentinelFieldsToGeneric() for Payment method/Amount/Currency,
and migratePeopleToGenericField() for People itself — both promote what
used to be a hardcoded special case (dedicated documents columns for
the former, the singleton document_people table for the latter) into
ordinary fields rows, copying across any value already saved under the
old shape. New fields, including new person-type fields, can also be
created directly from the capture/edit forms (a "+ Add a custom field"
toggle) — see Features above.
document_type_fields drives the capture/edit forms' dynamic field
behavior (see "Dynamic fields per document type" above): for a document
type present in this table, only the listed fields — People included,
since it's an ordinary field name here now, not a special case — show,
in the given order. A type absent from this table shows none of its
custom fields at all, matching Mariner's own behavior (fields must be
explicitly assigned to a type before they display). Populated by
migrate_to_new_library.py, which decodes Mariner's own per-type
display-field configuration; its 'People' rows needed no migration of
their own when People was promoted to a real field, since the column
already stored that literal string and keeps matching unchanged.
Any person-type field (People, Author, Collaborator, ...) works like
tags: a document can relate to more than one person through it (a joint
bill, co-authors, a shared appointment, etc.), so it's a many-to-many
relationship (document_field_people, keyed by which field as well as
which document), not a single string value — and every person-type field
shares the same underlying people table, so a name typed into Author
autocompletes and searches the same as one typed into People. For
migrated documents, People specifically is backfilled from Mariner's
"Person" custom field — which sometimes held multiple names joined with
"&" (e.g. "Arne & Jana") — split into individual people so that filtering
by one name finds every document they're part of, not just ones where
they're the only name. That "&"-splitting only ever happened once,
historically, in migrate_to_new_library.py's own migration step; within
the app itself, every person-type field has only ever used comma-separated
input.
subcategory is despite its name not nested under category — that's
how Mariner's own schema worked (no foreign key between the two tables),
and it holds in the data too: the same subcategory name shows up under
different categories on different documents (e.g. "Dentist" appears under
both "Medical" and "Health"). It's carried over as-is: a second, independent
classification field.
Unlike People, most custom fields are plain values, not split on "&". Person is genuinely multi-valued in practice ("Arne & Jana" means two people); most other fields aren't — a real "Organization" value can legitimately contain "&" as part of one name (e.g. "Dres. Ernestus & Cop, Sandhausen", a German medical practice partnership; "Stadtwerke Walldorf GmbH & Co. KG"), and splitting on it would corrupt the name rather than separate genuinely distinct values.
- No real Spotlight/Core Spotlight integration. A browser-based app has
no access to
CSSearchableIndexor the ability to register a Spotlight importer — both require native code installed at the system level. The.txtsidecar files get incidental Spotlight benefit (since Spotlight indexes any plain text file's content), but this is a workaround, not a true integration, and it doesn't cover PDFs without a text layer. - No direct scanner integration. A browser has no API to drive scanner
hardware or launch a native app like Image Capture or Windows Scan — the
capture form's "Need to scan a paper document first?" toggle only offers
instructions (tailored to your OS — Image Capture/Preview on macOS,
Windows Scan on Windows, a generic pointer elsewhere) for scanning outside
the app and then picking the resulting file with the normal file picker;
it can't trigger a scan itself. For a more
automated "scan → shows up ready to review" workflow, see the Inbox
feature and
scan_watch.pyabove — that still requires an explicit in-app click to actually add each file as a document, by design. - Reconnecting a recent library still needs one click. Browsers won't let a page silently regain filesystem access after a reload — even with a library remembered in the Recent libraries list (see Features above), reopening it takes one explicit click to re-confirm permission. This is a browser security requirement, not something Dossiary can skip.
- Searchable PDF generation works on JPEG/PNG images captured directly, not PDF uploads. Building the invisible, selectable text layer requires the source to be an image jsPDF can embed; a PDF you upload during capture is saved as-is, with no text layer added at capture time. This is distinct from OCR text extraction, which does work on PDFs — see "Re-run OCR" above — it just doesn't turn the PDF itself into a new, searchable one; the extracted text only fills the OCR text field. Other image formats (WEBP, GIF, TIFF) are similarly OCR'd for extracted text but not turned into a searchable PDF, since jsPDF's image embedding is only used here with JPEG/PNG.
- Searchable PDF text positioning is best-effort. Word bounding boxes come directly from Tesseract; horizontal stretching to exactly match each word's width isn't attempted (only position and approximate font size are), so the invisible text layer may not align pixel-for-pixel with the visible word underneath on close inspection — it should still select and search correctly.
- Preview generation only covers images and PDFs. Other file types (if you ever capture something else) won't get a preview — "Generate preview" will just report it can't handle that format.
- Field Settings itself still doesn't create new custom fields. The "⚙ Manage fields" dialog only lets you toggle/reorder which existing fields show per document type. Creating a brand-new field from scratch is done from the capture/edit forms instead — see "Add a custom field right from the capture/edit forms" above.
- Person-type custom fields (Author, Collaborator, etc.) can't become
table columns or filters. People itself keeps its own permanently-fixed
table column and filter dropdown, but that's a separate, older mechanism
— the generic
show_as_column/autocompletesystem every other custom field can opt into doesn't support multi-valued fields yet (rendering several names in one cell, or building a useful filter from them, is a distinct feature that hasn't been built). A new person-type field is fully usable everywhere else — capture, edit, detail view, search — just not as a column or filter. - Requires Chrome or Edge. Safari and Firefox don't support the write side of the File System Access API as of writing.
- Needs network on first load (to fetch the sql.js, Tesseract.js, jsPDF, and pdf.js WebAssembly/JS bundles from their CDNs) even though your documents never leave your machine.
MIT — see LICENSE.
There's a real, runnable Playwright regression suite in tests/ (46
scripts, no real user data — every test seeds its own synthetic library
state). Each is standalone: cd tests && python3 test_<name>.py. See
CLAUDE.md's "How this was tested" section for what's covered and how
the stubbing approach works, if you're making changes and want to verify
them the same way. See CONTRIBUTING.md for more.
Dossiary itself has no dependencies to install — everything below is loaded from a CDN at runtime, only when a feature that needs it is actually used (OCR, generating a searchable PDF, or rendering a PDF page for a preview/OCR). None of it ever touches your documents except locally, in your own browser.
| Library | Used for | License |
|---|---|---|
| sql.js | Reading/writing library.sqlite (SQLite compiled to WebAssembly) |
MIT |
| Tesseract.js | OCR text extraction | Apache-2.0 |
| jsPDF | Building the searchable PDF layer for captured images | MIT |
| pdf.js | Rendering a PDF's first page (for previews and OCR) | Apache-2.0 |
Same in-app, via the "Libraries" link in the footer.