A document format that's just JSON. Open .jdf in any text editor and you see the source. Open it in JDF Reader and you see a rendered page. Edit either side, the other reflects it. When the document carries embedded assets (images, fonts) it ships as .jdfx — a zip bundle around the same JSON, so a single file still travels self-contained.
JDF runs in three places:
| Surface | What it is | Install |
|---|---|---|
| JDF Reader | Native macOS app — read, edit, import PDF/MD, export PDF | brew tap uurtech/jdf && brew install jdf |
| jdf.js | JavaScript library — embed .jdf files on any web page |
npm install @uurtech/jdf or <script src="https://unpkg.com/@uurtech/jdf@0.1.11"> |
@uurtech/jdf-cli |
CLI for validating documents and converting from Markdown | npx @uurtech/jdf-cli validate file.jdf |
It's JSON. Every consequence below falls out of that:
cat,grep,jq, VS Code, every linter — they all work, no plugin.git diffshows the actual change, line-level.- Generating a doc is
JSON.stringify(doc). - A JSON Schema validates structure (
spec/jdf-schema.json) and powers IDE autocomplete. - Search is text search.
grep "TODO" *.jdfworks. - No vendor, no proprietary parser. Opens the same way today and in 20 years.
If a JSON-native document format gets global adoption, it becomes a base layer for knowledge. Then:
- Documents stop being "static files" — they become live, addressable structures.
- Everything becomes machine-readable by default — no parser stage, no OCR fallback.
- AI search becomes near-instant and accurate — retrieval against a real structure, not a guess at one.
- PDFs fade out the way fax did once email arrived.
- Every app plugs into the same document structure — one tree, every consumer.
The shift: instead of "AI trying to understand humans' files", humans start writing in a format already built for AI reasoning.
What that unlocks:
- Education content becomes instantly interactive.
- Legal & finance documents become queryable in real time.
- Knowledge graphs form automatically from documents.
- Every system becomes interoperable by default.
The real endgame: documents stop being storage → they become interfaces. That is the world-change scenario this format aims at.
brew tap uurtech/jdf
brew install jdfbrew clones the tap, downloads the latest .dmg from the GitHub release, and installs JDF Reader.app into /Applications.
Upgrade later:
brew upgrade --cask jdfThe Cask formula lives in a separate tap repo: uurtech/homebrew-jdf/Casks/jdf.rb. A reference copy is also kept in this repo at Casks/jdf.rb.
Linux and Windows builds (.deb, .AppImage, .rpm, .msi, .exe) are produced by the GitHub Actions release workflow on every tag — see the latest release.
Embed JDF documents on any web page with one tag:
<link rel="stylesheet" href="https://unpkg.com/@uurtech/jdf@0.1.11/dist/jdfjs.css">
<script type="module" src="https://unpkg.com/@uurtech/jdf@0.1.11"></script>
<jdf src="/whitepaper.jdf"></jdf>Or via npm for full programmatic control:
npm install @uurtech/jdfimport { embed } from "@uurtech/jdf";
import "@uurtech/jdf/style.css";
await embed("#viewer", "/doc.jdf", { zoom: 1.2, sidebar: true });See the jdf.js README and the embed documentation for the full API, attribute reference, and framework integrations (React / Vue / Svelte).
git clone https://github.com/uurtech/jdf.git
cd jdf
pnpm install
pnpm tauri build # produces .app + .dmg in apps/reader/src-tauri/target/release/bundle/Requires Node 20+, pnpm 9+, Rust stable, Xcode CLT (macOS).
Open: drag any .jdf, .jdfx, .pdf, or .md onto the welcome screen, double-click in Finder (file associations are registered for both .jdf and .jdfx), or Cmd+O.
Edit a paragraph: double-click it. The whole paragraph (or heading, list item, table cell, collapsible title, image src/alt) becomes an inline editor. Type. Press Enter or click anywhere else — the change saves to disk in ~150 ms.
Restructure: hover any element. A floating toolbar pops up in the top-right corner with ↑ Move up · ↓ Move down · ⧉ Duplicate · × Delete. No right-click, no menu hunting.
Insert new elements: the Insert bar at the top of every page lets you append a Text / Rich text / List / Table / Shape / Image / Section / TOC element with a single click.
Pages: the sidebar shows a thumbnail preview of every page. Click + for a new page. Hover any thumbnail and click the red × to delete it.
Undo / redo: ⌘Z / ⌘⇧Z — 100-step history. Includes every text edit, structural change, and JSON view commit.
Multiple windows: ⌘N or the toolbar "New" button. Compare two documents side-by-side.
Memory model:
- A
.jdfor.jdfxopens in memory and auto-saves to its source file on every commit. - A
.pdfor.mdis converted to JDF in memory only — the original file is never touched. The toolbar shows● Unsaved (in memory)while you edit. If you close the window with unsaved changes, you get a prompt to save it (.jdfxif it has assets,.jdfotherwise) or discard. - The JSON view is a live two-way bind. Edit JSON, blur or
Cmd+S, and the rendered view follows. Edit visually, the JSON updates as you go.
JDF lives in three runnable surfaces. They all consume both .jdf (plain JSON) and .jdfx (zip bundle) but expose different feature sets — the desktop Reader is the only place you edit, jdf.js is a renderer, the CLI is for validation and conversion.
Legend: ✓ supported · ◐ partial / planned · — not applicable
All three renderers walk the same JSON. The desktop Reader and jdf.js are kept at strict feature parity for everything below.
| Capability | JDF Reader | jdf.js | CLI |
|---|---|---|---|
text element (heading 1-6, align, link, tocEntry, style) |
✓ | ✓ | — |
richtext element (per-run bold/italic/underline/strikethrough/color/font/link) |
✓ | ✓ | — |
image element (base64 resource OR src URL/path; fit modes) |
✓ | ✓ | — |
table element (headers, colspan/rowspan, alternating rows, borders, column align) |
✓ | ✓ | — |
list element (ordered/unordered, nested with per-item type override) |
✓ | ✓ | — |
shape element (rect, circle, ellipse, line, SVG path; fill/stroke/opacity) |
✓ | ✓ | — |
collapsible element (expandable section with nested elements) |
✓ | ✓ | — |
toc element (auto-generated, hierarchical, click-to-navigate) |
✓ | ✓ | — |
Page sizes A4/A3/A5/Letter/Legal/Tabloid + custom {width,height} |
✓ | ✓ | — |
| Portrait / landscape (doc-level + per-page override) | ✓ | ✓ | — |
| Margins (doc-level merged with per-page) | ✓ | ✓ | — |
| Headers / footers (template strings AND full element trees) | ✓ | ✓ | — |
Internal links (#page-N navigation) |
✓ | ✓ | — |
| External links (open in new tab) | ✓ | ✓ | — |
Named styles (styles.foo referenced as string / array / inline) |
✓ | ✓ | — |
| Dark mode | ✓ | ✓ | — |
| Multi-page scroll + page indicator | ✓ | ✓ | — |
| Sidebar with page thumbnails | ✓ | ✓ (opt-in via sidebar) |
— |
| Toolbar (zoom, page nav, search) | ✓ | ✓ (opt-in via toolbar) |
— |
Fit modes (fit-width, fit-page, manual zoom) |
✓ | ✓ | — |
Reactive attributes — change src/width/zoom and the viewer updates |
— | ✓ | — |
Editing lives in the desktop Reader only — jdf.js is a viewer, the CLI is non-interactive.
| Capability | JDF Reader | jdf.js | CLI |
|---|---|---|---|
| Double-click any element to edit in place | ✓ | — | — |
| Inline editor for headings, paragraphs, list items, table cells, collapsible titles, image src/alt | ✓ | — | — |
| Auto-save to disk (~150 ms after edit) | ✓ | — | — |
| Hover toolbar — Move up / Move down / Duplicate / Delete (no right-click) | ✓ | — | — |
| Insert bar — Text / Rich text / List / Table / Shape / Image / Section / TOC | ✓ | — | — |
| Page management (add page, delete page, drag thumbnails) | ✓ | — | — |
Undo / redo (⌘Z / ⌘⇧Z, 100-step history) |
✓ | — | — |
| Live JSON view with two-way bind | ✓ | — | — |
Multiple windows (⌘N) |
✓ | — | — |
File associations (double-click .jdf in Finder) |
✓ | — | — |
| Capability | JDF Reader | jdf.js | CLI |
|---|---|---|---|
Open .jdf from disk |
✓ | ✓ (via <jdf src> / embed()) |
✓ (validate) |
Import .md → .jdf |
✓ | — | ✓ (jdf import file.md) |
Import .pdf → .jdf (full fidelity: positions, fonts, colors, shapes, embedded images) |
✓ | — | ◐ (planned — currently delegates to the desktop importer) |
| JSON Schema validation | ✓ (live, in-app) | — | ✓ (jdf validate file.jdf) |
| Markdown viewer (native render, no conversion) | ✓ | — | — |
| Capability | JDF Reader | jdf.js | CLI |
|---|---|---|---|
Export to PDF (Cmd+Shift+E) — preserves text, images, vector shapes, fonts, colors |
✓ | — | ◐ (planned) |
Save edits back to source .jdf |
✓ (auto) | — | — |
Save imported PDF/MD as .jdf |
✓ | — | — |
| Surface | How to get it |
|---|---|
| JDF Reader (macOS) | brew tap uurtech/jdf && brew install jdf — DMG / .app, signed via GitHub release |
| JDF Reader (Linux / Windows) | .deb / .AppImage / .rpm / .msi / .exe from the latest release |
| jdf.js | npm install @uurtech/jdf or <script src="https://unpkg.com/@uurtech/jdf@0.1.11"> |
@uurtech/jdf-cli |
npx @uurtech/jdf-cli validate file.jdf (no install) |
Page sizes: A4, A3, A5, Letter, Legal, Tabloid, custom ({width, height} in mm). Portrait or landscape — set at the document level (meta.pageOrientation) or per-page. Margins are merged: doc-level meta.margins + per-page overrides. Headers and footers accept either a template string ({{pageNumber}} {{totalPages}} {{title}} {{author}}) or a full element tree.
Element-by-element capabilities are listed in the Feature matrix above; the JSON Schema in spec/jdf-schema.json is the source of truth.
Drag a .pdf onto the viewer and you get an editable JDF copy that looks identical to the original — no "best effort", no placeholders.
Per text run, the importer extracts:
- position (mm) — via PDF.js
viewport.convertToViewportPoint, accounting for rotation, CropBox, and MediaBox offset. - font family — looked up from PDF.js
commonObjscache, mapped toInter / Times New Roman / JetBrains Monobased on the original font name. - font size in points (from the text matrix scale).
- bold / italic — detected from the real font name (
Helvetica-Bold,Times-Italic, etc). - color — from
setFillRGBColor / setFillGray / setFillCMYKColorwalked over the operator list, snapshotted at each text-show op. - opacity — from
ca/CAinsetGState. - invisible text — text rendering mode 3 (used for OCR layers) is filtered out.
- link annotations —
getAnnotations()rectangles are matched to text runs and emitted as JDFlinks.
For graphics:
- Vector shapes: rectangles, lines, and arbitrary paths from
constructPathare emitted asshape: rect | line | pathwith their fills, strokes, stroke widths, andopacity. Cubic and quadratic Bezier curves preserved as SVGCsegments. - Embedded images:
paintImageXObjectops are followed back topage.objs, decoded into RGBA via canvas, encoded to base64 PNG, stored inresources.images, and placed at their original transform on the page.
The result: PDF heading → JDF heading element with right size, right font, right color, right position. PDF table → individual cell text elements at the right grid coordinates. PDF logo → embedded base64 image at its real placement. Then you double-click any of it to edit.
Round-trip back to .pdf via the toolbar (or Cmd+Shift+E). Respects:
meta.pageSizeandpageOrientation(A4 / A3 / A5 / Letter / Legal / Tabloid / custom; portrait / landscape; doc-level + per-page overrides).style.coloron every text element viaset_fill_color.- Text, richtext, lists, tables, collapsibles, shapes — all rendered.
- Embedded images: base64 → image crate decoder → printpdf
ImageXObject. The Markdown / PDF imports' images come back out the other end. - TOC — iterated from the document's headings into a real PDF table-of-contents.
.md opens with a continuous-scroll, GitHub-style render (marked, full GFM: tables, blockquotes, code, links, images, task lists, hr, strikethrough). Toolbar toggle flips to the paged JDF render of the same content. Cmd+F highlights matches inline with <mark> tags in the live MD output, line-by-line.
Images in Markdown —  works with relative paths (komojam_target_architecture.drawio.png), absolute paths, and http(s):// URLs. On import the relative ones are read from disk and base64-embedded into the document so the resulting .jdf is self-contained and portable. Both the JDF Reader app and the CLI (jdf import file.md) follow the same rule.
JDF ships in two file shapes for the same schema. Both open the same way; the difference is how binary assets are stored.
| Shape | Layout | When it's used | Why |
|---|---|---|---|
.jdf |
Single JSON file | No embedded assets — only text, http URLs, or trivially small inline data | Stays diffable, cat/grep/jq work on the file directly |
.jdfx |
ZIP bundle: document.json + manifest.json + assets/* |
One or more embedded images / fonts | Self-contained (one file to share), no base64 bloat in JSON, RAG can decide per-element whether to fetch the binary |
The reader, jdf.js web embed, CLI, and PDF exporter all open both. The save flow picks .jdfx automatically when the document has any embedded asset; you can override the choice in the Save As dialog.
hello.jdfx (zip)
├── document.json ← JDF document — same schema as a standalone .jdf
├── manifest.json ← format metadata: version, generator, asset listing
└── assets/
├── asset-1.png
└── asset-2.jpg
Scope rule: document.json.meta owns content metadata (title, author, keywords, description). manifest.json owns format metadata (version, generator, asset listing). They never duplicate fields — document.json is the source of truth. Manifest schema: spec/jdfx-manifest-schema.json.
| Form | Used in | Example |
|---|---|---|
| Bundle resource | .jdfx (default) |
{ "type": "image", "resource": "asset-1", "alt": "..." } — bytes live in assets/asset-1.png |
data: URL |
Inline in .jdf (small images / no zip) |
{ "type": "image", "src": "data:image/png;base64,iVBORw0KGgo..." } |
http(s):// URL |
CDN-hosted, shared figures | { "type": "image", "src": "https://cdn.example.com/diagram.png" } |
fit accepts contain (default), cover, fill, none. The renderer in the desktop app, the web embed, and the PDF exporter all resolve the three forms identically — no missing-image fallbacks anywhere in the pipeline.
 works with relative paths, absolute paths, and http(s):// URLs. On import:
- Relative paths are resolved against the Markdown file's directory and read from disk.
- Bytes get embedded into the document.
- If at least one image was embedded, the importer writes a
.jdfx; otherwise a plain.jdf. Same rule in the desktop reader andjdf import file.md.
JDF removes most of the work a typical retrieval-augmented-generation pipeline does on PDFs. The structure that PDF parsers try to reconstruct is already in the file, so several pipeline stages become trivial or vanish entirely:
| Stage | JDF | |
|---|---|---|
| Parse / extract | pdfplumber / pymupdf / unstructured — layout analysis, font heuristics, OCR fallback for image-only pages |
JSON.parse(content) — no layout reconstruction, the structure is already in the file |
| Chunking | Token-windowed splits that frequently slice through tables, lists, footnotes | Each element (text, richtext, table, list, image) is a natural retrieval unit — no chunker config |
| Metadata | Synthesized after the fact (page number, "is this a heading?") and often wrong | First-class on every element: type, heading, page index, position, link target |
| Embedding noise | Repeated page headers / footers / page numbers leak into chunks | header and footer live in their own tree, never in content chunks |
| Re-indexing on edit | Re-parse + re-chunk + re-embed the whole PDF | Diff the JSON, re-embed only the changed elements |
| Tables | Cells smear across columns; multi-row headers collapse | { headers: [...], rows: [[...]] } — every cell at its real coordinate |
| Images | Dropped or stubbed as [image] |
Stored in resources.images with alt text and an anchor element — a vision step can fetch the image at the exact retrieval point |
Benchmarks coming. The wins above are structural — pipeline stages JDF removes entirely — not measured timings. We're running benchmarks on a public corpus (academic PDFs, financial filings, scanned reports) covering parse, chunk, embed, and retrieval cost; this section will be updated with the numbers as soon as they're ready. Real-world speedup depends on your PDFs (text-only vs. scanned), parser, and chunker — if you run a comparison on your own corpus first, please open an issue with the methodology and we'll include it.
A minimal RAG ingestor for JDF is a single loop — no PDF library, no layout heuristics, no chunker config:
import fs from "node:fs/promises";
import type { JdfDocument } from "@jdf/core";
const doc: JdfDocument = JSON.parse(await fs.readFile("paper.jdf", "utf8"));
for (const [pageIndex, page] of doc.pages.entries()) {
for (const el of page.elements) {
const text =
el.type === "text" || el.type === "richtext" ? el.content :
el.type === "list" ? el.items.map(i => i.content).join("\n") :
el.type === "table" ? [el.headers, ...el.rows].map(r => r.join(" | ")).join("\n") :
null;
if (!text) continue;
await index.upsert({
id: `${doc.meta?.title}-p${pageIndex}-${el.type}`,
vector: await embed(text),
metadata: {
type: el.type,
heading: (el as any).heading ?? null,
page: pageIndex + 1,
title: doc.meta?.title,
},
});
}
}The same pipeline against a PDF needs pdfplumber (or equivalent), a layout heuristic to detect headings, a chunker that respects tables, and an OCR fallback for image-only pages — and still loses fidelity at every step.
See docs/docs/why-ai.html for the long-form discussion of why every modern LLM reads JDF more easily than PDF.
{
"$jdf": "1.0.0",
"meta": { "title": "...", "pageSize": "A4", "unit": "mm" },
"styles": { "heading": { "fontSize": 22, "fontWeight": "bold" } },
"resources": { "images": { "logo": { "data": "<base64>", "mimeType": "image/png" } } },
"header": { "content": "{{title}}" },
"footer": { "content": "page {{pageNumber}} / {{totalPages}}" },
"pages": [
{
"elements": [
{ "type": "text", "content": "Hello", "heading": 1, "position": { "x": 0, "y": 5 }, "width": 166 },
{ "type": "list", "listType": "unordered", "items": [{ "content": "one" }, { "content": "two" }], "position": { "x": 0, "y": 25 }, "width": 166 }
]
}
]
}Positions in mm (default), font sizes in pt. A4 content area: 166 × 247 mm with the default 22 / 25 mm margins.
Full schema: spec/jdf-schema.json. Working example: spec/examples/hello-world.jdf.
Internal navigation: link: "#page-3" or link: { type: "internal", target: "#page-3" } on text/richtext.
@uurtech/jdf (sources in jdfjs/) is a small JavaScript library that turns any .jdf URL into a fully styled, scrollable, searchable embed in a web page. Like PDF.js — but the file is plain JSON.
<jdf src="/doc.jdf"></jdf>
<!-- Configure via attributes -->
<jdf src="/doc.jdf"
width="800"
height="600"
zoom="1.2"
sidebar="true"
dark-mode="auto"></jdf>That's the only embed form. Every <jdf> tag on the page is auto-detected on DOMContentLoaded and rendered. New tags added later (SPAs, async content) are picked up by a MutationObserver. To opt out per element: add manual. To disable globally: window.JDFjsAutoInit = false before loading the script.
| Attribute | Type | Default |
|---|---|---|
src |
string | required |
width |
number (px) or any CSS length | — |
height |
number (px) or any CSS length | 600px |
zoom |
number | 1 |
fit |
"manual" · "fit-width" · "fit-page" |
"manual" |
sidebar |
boolean | false |
toolbar |
boolean | true |
dark-mode |
"auto" · "light" · "dark" |
"auto" |
page |
integer (0-based) | 0 |
manual |
boolean | — |
import { embed, render, JDFViewer } from "@uurtech/jdf";
import "@uurtech/jdf/style.css";
// 1. Embed by URL
const v = await embed("#viewer", "/doc.jdf", {
zoom: 1.2,
sidebar: true,
darkMode: "auto",
width: "100%",
height: "80vh",
fit: "fit-width",
onPageChange: (i) => console.log("page", i),
});
v.goToPage(2);
v.setZoom(1.5);
// 2. Render an in-memory document (no fetch)
import type { JdfDocument } from "@uurtech/jdf";
const doc: JdfDocument = { $jdf: "1.0.0", meta: { title: "Hi" }, pages: [...] };
render("#out", doc);The library bundles to dist/jdfjs.js (~25 kB minified + gzipped). No framework, no build dependencies. Browser support: Chrome 88+, Firefox 87+, Safari 14+, Edge 88+.
Full reference: jdfjs/README.md · docs/docs/embed/.
# run on demand (no install)
npx @uurtech/jdf-cli validate doc.jdf
npx @uurtech/jdf-cli import README.md # → README.jdf
npx @uurtech/jdf-cli import paper.pdf -o out.jdf
# or install globally
npm install -g @uurtech/jdf-cli
jdf validate doc.jdfvalidate runs Ajv against the JSON Schema and reports path-level errors plus warnings. import accepts .md (works) and .pdf (planned — currently delegates to the desktop importer).
When working from a clone of this repo, the dev entry point is pnpm --filter @uurtech/jdf-cli start <subcommand> — same arguments, runs from source via tsx.
| Shortcut | Action |
|---|---|
Cmd+O / Cmd+W |
Open / close |
Cmd+N |
New window |
Cmd+S / Cmd+Shift+E |
Save As .jdf / Export PDF |
Cmd+Z / Cmd+Shift+Z |
Undo / redo |
Cmd+F |
Search |
Cmd+B |
Sidebar |
Cmd+D |
Dark mode |
Cmd+P |
|
Cmd+= Cmd+- Cmd+0 |
Zoom |
← → |
Page nav |
| Double-click | Edit element |
Enter / Esc / Cmd+Enter |
Commit / cancel / multi-line commit |
? |
Shortcut overlay |
spec/ JSON Schema + examples
packages/jdf-core/ TypeScript types + utils
jdfjs/ jdf.js — web embed library (npm: @uurtech/jdf)
apps/reader/ Tauri v2 app
src/
components/ element renderers, JSON view, MD view, sidebar, toolbar
edit/ mutation API + undo/redo history
import/ PDF.js → JDF converter
src-tauri/ Rust backend (MD parse, PDF export with image embed, search)
tools/jdf-cli/ Ajv validate + MD→JDF importer
Casks/ Homebrew cask formula
.github/workflows/ CI (typecheck, schema validate, cargo check on 3 OSes)
+ release (tag → multi-OS bundles)
Stack: Tauri v2, SolidJS, Tailwind v4, Vite, Rust (pdf-extract, printpdf, pulldown-cmark, image), Ajv, marked, pdfjs-dist.
Done:
- Full element rendering, edit-in-place + auto-save, JSON view, Markdown viewer.
- PDF import with positions, fonts, colors, opacity, links, vector shapes, embedded images.
- PDF export with page size, orientation, colors, real TOC, embedded images.
- Structural editing: hover action bar (move / duplicate / delete), Insert bar, page add/delete in sidebar.
- Undo / redo (100 steps, all mutations).
- Multiple windows (
⌘N). - macOS / Linux / Windows builds via GitHub Actions release workflow.
- JSON Schema, CLI validate, CI on all three OSes.
- Homebrew tap (
uurtech/jdf). - jdf.js — web embed library with auto-init, single
<jdf src="...">form, feature parity with the desktop renderer. - Published to npm as
@uurtech/jdf— install vianpm install @uurtech/jdfor load from CDN athttps://unpkg.com/@uurtech/jdf@0.1.11(always pin a version in production). .jdfxzip bundles — automatic for documents with embedded images/fonts. Reader, jdf.js, and CLI all read and write the format; manifest schema atspec/jdfx-manifest-schema.json.- Markdown image imports —
works in both the desktop importer andjdf import file.md. Relative paths are resolved against the source file's directory and embedded into the output bundle.
See CHANGELOG.md for the per-release log.
The next surface area, grouped by theme. Items at the top of each group are scheduled first.
- Public benchmark suite — parse / chunk / embed / retrieval cost measured on a shared corpus (academic PDFs, financial filings, scanned reports). Results published at
docs/docs/benchmarks.htmland linked from the RAG section. Backs the structural claims indocs/docs/why-ai.htmlwith real numbers. @uurtech/jdf-rag— RAG-ready ingestor as a published package. One import gives you an iterator of typed chunks (text/richtext/table/list/image) each carrying first-class metadata (page index, type, heading level, link target). No PDF library, no chunker config.@uurtech/jdf-llm— structured-output helpers for the major LLM APIs (OpenAIresponse_format, Anthropictools, GoogleresponseSchema). Ships the JDF JSON Schema as a guaranteed-valid generation target plus prompt scaffolding for "produce a one-page report" workflows.
jdf import file.pdf— full PDF import in the CLI. Extract the existing browser importer (apps/reader/src/import/pdfToJdf.ts) intopackages/jdf-pdf-import/with two entry points:browser.ts(canvas) andnode.ts(node-canvas). Desktop and CLI consume the same algorithm — no duplication.jdf export file.jdf -o file.pdf— PDF export in the CLI. Wraps the Rust exporter as a standalone binary or ports it to JS.
- PDF table detection — geometry-based row/column grouping during PDF import. Today the importer emits cells as positioned
textelements; this pass groups them into realtableelements withheaders+rows. The single biggest fidelity win for RAG retrieval and export round-trips. - Multi-page overflow on PDF export — content longer than the page flows to page N+1 instead of being clipped. Currently the exporter assumes one logical page per
pageentry. - Editing in jdf.js — opt-in editor mode (
<jdf src="..." editable>) that mirrors the desktop reader's inline editing, hover action bar, andCmd+Ssave. Today jdf.js is strictly a renderer.
- VS Code extension —
.jdfpreview pane, JSON Schema-driven autocomplete, outline tree, click-to-jump from the JSON to the rendered element. - Format version 1.1 — additive: more
styleproperties (text shadow, gradient fills),footnoteelement,columnlayout primitive,resources.fonts(loaded fromassets/in.jdfxbundles). Schema bump documented inCHANGELOG.mdper the parity rules inCLAUDE.md.
- Apple notarization — notarized macOS build silences Gatekeeper entirely. Removes the
xattr -crworkaround currently shipped in the Homebrew Cask. - Linux & Windows code signing — sign
.deb/.rpm/.msi/.exeartifacts in the GitHub Actions release workflow. - Auto-bump CDN pins on release —
scripts/release.shrewrites everyunpkg.com/@uurtech/jdf@<old>reference inREADME.md,jdfjs/README.md, anddocs/**/*.htmlto the new version before tagging. Removes a class of "demo broke after release" bugs.
JDF is open source — fork the repo, hack on it, open a pull request. CONTRIBUTING.md has the full guide; the short version:
- Fork → branch → make your change.
pnpm typecheckandcargo check(inapps/reader/src-tauri/) must pass.- Add a sample to
spec/examples/if your change affects rendering. - Open a PR against
master. CI runs the same checks on every PR.
When adding a new JDF element type or attribute, update all five locations: packages/jdf-core/src/types.ts, spec/jdf-schema.json, apps/reader/src/components/viewer/, jdfjs/src/renderers/element.ts, and apps/reader/src-tauri/src/commands/mod.rs. See CLAUDE.md for the full parity checklist.
Bug reports, feature requests, and design discussions all welcome in GitHub Issues.
MIT — LICENSE.

