A native macOS study app: every lecture file in one dark, offline library,
with a real Excalidraw board to explain ideas and a map of how your material connects.
Opens .pptx / .ppt / .pdf / .docx, Markdown, notebooks, spreadsheets and
code full-screen with a smart dark mode, built for long revision sessions.
No account, no cloud, no telemetry: the app talks only to 127.0.0.1.
Download SlideView.zip, unzip it and drag SlideView.app to Applications. Apple Silicon, macOS 13 or later.
First launch: the app is not notarised by Apple, so macOS blocks it once. Open System Settings → Privacy & Security, scroll down and click Open Anyway, or run
xattr -dr com.apple.quarantine /Applications/SlideView.app.
Or build it yourself: ./build.sh → ~/Applications/SlideView.app.
If SlideView saves you a late night, a ⭐ helps other students find it.
Text and vector art are inverted; photographs and diagrams keep their real
colours, and a red heading stays red. D cycles Smart, Invert, Dim and Light.
| Smart | Light (original) |
|---|---|
![]() |
![]() |
The real Excalidraw editor, embedded
and offline. Sketch a concept next to the deck it belongs to; the drawing is an
ordinary .excalidraw file in your library. More in Drawing board.
Press M for a force-directed map: documents that share topics are linked
automatically, and [[wikilinks]] in your notes become edges. More in Map.
⌘N creates plain text, Markdown, a CSV table or a drawing in any library
folder, saved as a normal file. More in Notepad.
- Tabs, Chrome-style, each with its own page, zoom, stars and notes
- Per-slide notes, starred slides, text search and a thumbnail rail
- Zen mode (
H): nothing on screen but the slide - Right-click a slide to copy it, share it, or send it to Google Lens or Gemini
- 40+ formats normalised to PDF once, then cached, so re-opening is instant
- Credentials files are never scanned or cached
Everything is normalised to a PDF once, then cached — so every format gets the same treatment: thumbnails, filmstrip, tabs, text search, per-slide notes, smart invert, Google Lens and Gemini.
| Formats | Rendered by | |
|---|---|---|
| Slides | pptx ppt odp key pps ppsx |
LibreOffice |
| Documents | pdf · docx doc odt rtf rtfd html · pages |
direct · TextKit · LibreOffice |
| Spreadsheets | csv tsv · xlsx xls ods numbers |
in-process · LibreOffice |
| Notes | md markdown rmd txt tex org rst |
in-process |
| Notebooks | ipynb |
in-process |
| Images | png jpg jpeg heic heif gif webp tiff bmp |
PDFKit — off by default, see below |
| Drawings | excalidraw |
Excalidraw's own exporter — see Drawing board |
| Code | swift py c cpp java js ts json yaml sql … |
in-process |
Jupyter notebooks keep their structure: markdown cells, In [n] prompts, code
cells, stream and error output, and inline plots (decoded from the notebook's
own base64, so no kernel is needed).
Images are excluded from scanning by default, behind Include images in the sidebar. A folder of study material almost always also holds screenshots, scans and ID photos, and indexing those buries the decks. They stay openable on demand.
Credentials files are never scanned, converted or cached. Anything that
looks like secrets — .env and .env.*, a file called env/env.txt,
credentials.*, secrets.*, id_rsa, .npmrc, .netrc, and .pem .p12
.pfx .keystore .jks .ppk .asc .gpg — is skipped. Rendering one would
leave a readable copy of your keys in ~/Library/Application Support/SlideView,
which is exactly the thing you would not want. Open one deliberately and it goes
straight to the text editor, which reads the file directly and caches nothing.
Any render made before this rule existed is deleted the next time the library is
scanned. (.key is not on the list — that is Keynote.)
Code and config files are opened on demand but never scanned. Indexing every
.js and .json under a project folder would bury the actual material — so the
library stays clean, while ⌘O, drag-and-drop and Finder's Open With will open
anything in the table above. Build and dependency directories (node_modules,
.git, venv, DerivedData, Pods, build, …) are skipped when scanning.
Press M, or Map in the library header, for a force-directed view of everything you study from. Two kinds of edge:
- Written links (blue) —
[[wikilinks]]and](file)references in your notes, resolved against the library. - Shared topics (grey) — significant vocabulary two documents have in common, scored with TF-IDF so that frequent words don't tie everything to everything. Each document keeps a signature of its ~28 strongest terms; edges are capped at four per node so the map stays readable.
Signatures are cached per converted PDF, so the map is only expensive the first
time. Hover a node for its top terms, click to open it, drag to rearrange,
scroll or pinch to zoom, double-click to reframe. F searches, + − 0 zoom.
It is built to stay usable at a few hundred documents: labels are placed in screen space with collision rejection (and capped) rather than drawn for every node, repulsion uses a distance cutoff so the layout does not crawl, the subject legend hides behind a button, and a click only opens a document if the pointer did not move — dragging a node used to open it by accident.
On the apple-intelligence-assistant
branch, pressing A opens an assistant that answers questions from your own decks, writes
study guides for a subject, keeps your chats and remembers what you tell it. It runs on the Mac
itself, with Apple's on-device model or any Ollama model, and every answer shows the page it came
from. It is not in a release yet: small models still misread diagrams and pad answers. The
branch's README lists how it works and what is left to improve. To try it:
git checkout apple-intelligence-assistant
./build.sh
Worth stating plainly, because it constrains what this repo can contain:
| Project | What it does | Licence |
|---|---|---|
| NightPDF | Dark-mode PDF reader, Electron + PDF.js | GPLv2 |
| Logseq | Local notes with a graph view | AGPL-3.0 |
| Obsidian | Notes with a graph view | closed source — the org holds docs, API typings and sample plugins only; there is no app source or backend to connect to |
| Skim | macOS PDF reader/annotator | BSD |
| Sioyek | Research-paper reader | GPLv3 |
| Excalidraw | Hand-drawn style whiteboard | MIT |
Copying from the GPL/AGPL projects would force this MIT repo to relicense, and Obsidian has nothing to copy. So the ideas were worth studying and the code was not: the smart-invert pass, the map, and every converter here are written from scratch.
Excalidraw is the exception, and the licence is why: it is MIT, like this repo, so the drawing board embeds the real editor instead of imitating it. The vendored third-party code is PDF.js (Apache 2.0) and Excalidraw with its dependencies (MIT and other permissive licences), all credited in web/vendor/NOTICE.md.
New drawing on the library's front page (or ⇧⌘N) creates an .excalidraw
file and opens it in the real Excalidraw
editor, version 0.18.1, embedded in the app: every shape and tool, hand-drawn
and architect styles, arrows that bind to shapes, elbow arrows, frames, images,
the laser pointer, shape libraries, Mermaid-to-diagram, and PNG / SVG / clipboard
export.
A drawing behaves like every other SlideView document:
- It is an ordinary file in your library folder, in Excalidraw's standard format — the same file opens on excalidraw.com or in any other Excalidraw.
- It saves itself as you draw, and again before the app quits.
- It has a thumbnail in the library, the labels written on it feed the map, and Preview opens it as a slide with notes, stars and smart invert like any deck.
- Rename, Trash, Share and Reveal work on it from the
⋯menu.
Images can be pasted, picked from disk, or dropped straight onto the canvas.
Your shape library is shared across all drawings and kept in
~/Library/Application Support/SlideView/library.excalidrawlib.
Three excalidraw.com features depend on Excalidraw's own servers rather than on the editor, so they are absent here by design:
- Live collaboration needs a relay server and a shared room.
- Shareable links upload the scene to Excalidraw's backend.
- AI text-to-diagram calls Excalidraw's AI service. Mermaid-to-diagram, which runs locally, is included.
The 12 MB CJK handwriting font is also left out to keep the app small; those characters fall back to a system font. The drawing page refuses every request that is not to the app's own local server, so the editor cannot quietly reach for a CDN.
Excalidraw's published build leaves about thirty dependencies as bare imports,
so it cannot be served as-is. Tools/vendor-excalidraw.sh bundles it once with
esbuild into web/vendor/excalidraw/ — committed, so building the app still
needs no Node. The editor runs in an iframe (web/draw.html, web/draw.js), so
its React tree and stylesheet stay out of the rest of the app and nothing loads
until a drawing is opened.
Thumbnails come from Excalidraw's own exporter, the only renderer that draws a scene exactly as the editor does. The editor posts a picture after each save; a drawing that arrives from elsewhere is rendered by the same page in an offscreen web view. A scene that fails to parse is never replaced with an empty one — the editor declines to open it and leaves the file alone.
A bare WKWebView ignores file pickers, downloads, confirm() and
window.open(), all of which Excalidraw relies on, so the app implements them:
insert image opens a panel, export opens a save dialog, and web links go to
your browser. Excalidraw's own shortcuts (⌘Z, ⌘D, ⌘G…) take priority inside
a drawing: WebKit offers a key chord to the page first, and only passes it on to
the menu bar if the page does not use it.
⌘N creates a note in any library folder and opens it in a simple full-window
editor — type and it saves itself, ⌘S forces a save.
Pick the type in the sheet: Plain text .txt, Markdown .md, Table
.csv, or Other for any other editable extension (.tex, .py, .json, …).
Typing an extension into the name selects the matching type on its own, and the
sheet always shows the filename you will end up with. A new Markdown note starts
with its own # title; the rest start empty.
.txt files open straight into the editor rather than the formatted view;
Markdown, CSV and code files open formatted with an Edit button (E) to
switch. Preview flips back, re-rendering from what you just wrote.
Notes stay ordinary files on disk — no database, no proprietary format — so they show up in the map alongside everything else and can be opened by anything.
Right-click any slide or card — or ⋯ in the editor — for Rename… and
Move to Trash. Both confirm first, and deletion goes to the Trash, never
straight to an unrecoverable delete, so a mistake is undone from Finder.
Renaming changes a document's identity (the id is a hash of its path), so the notes file is carried across and the UI migrates reading position and starred slides to the new id. Restoring a file from the Trash to its old path brings its notes back with it, since notes are deliberately not purged on delete.
Share opens the standard macOS share sheet (AirDrop, Mail, Messages, Notes),
⇧⌘S anywhere, or right-click a slide for Share… / Share as PDF… — handy
for sending a converted deck to someone who has no PowerPoint. Copy puts the
note's text on the clipboard; Copy File puts the file itself there, so it can
be pasted straight into Finder or a message.
- Open With — SlideView registers for every type above, as an alternate handler, so it never steals your existing defaults.
- Drag and drop — drop files anywhere on the window to open them, or drop a folder to add it to the library.
- Open Recent in the File menu, backed by the system recents list.
- Full menu bar — File, Edit, View (appearance, zen, zoom), Go (slides and starred), Window (tabs) and Help.
- Files opened from outside your library folders appear under Opened files.
Menu shortcuts deliberately all carry ⌘, so the single-key shortcuts stay free for the viewer and cannot fire while you are typing a note.
- PowerPoint files are converted to PDF once by LibreOffice in the background,
then cached in
~/Library/Application Support/SlideView/pdf. Re-opening is instant; editing the source re-converts automatically. - Everything runs fully offline. PDF.js is vendored into the app bundle and
the HTTP server binds to
127.0.0.1only. - Conversion uses an isolated LibreOffice profile, so it works even while the LibreOffice app is open with the same file.
| Mode | What it does |
|---|---|
| Smart | Inverts text and vector art, leaves photographs in true colour |
| Invert | Inverts the whole slide |
| Dim | Keeps colours, lowers the light output — for already-dark decks |
| Light | Original |
Inversion is per-channel invert plus a 180° hue rotation, so a red heading stays
red rather than turning cyan, and the result is compressed into a soft
near-black / near-white range instead of harsh #000 / #fff.
Decks open in tabs across the top of the window, Chrome-style, always visible — the library acts as the new-tab page.
⌘T open another deck · ⌘W close tab · ⌘1…⌘9 switch · ⌃⇥ next tab
⌘-click or middle-click a deck in the library to open it in a background tab.
Two macOS details worth knowing if you touch this code:
- Tabs sit in the titlebar strip. Clicks do reach the web view there, but the
window then loses its drag area — so the shell puts a real
NSViewwithmouseDownCanMoveWindowin the empty space right of the tabs, and the page reports where that space begins (dragZone). - In full screen the strip folds into the toolbar row instead of keeping a row of its own, which would cost ~42px of slide for nothing.
Each tab keeps its own page, zoom, stars, search index and notes. Render caches are keyed per document, so switching back to a tab is instant.
H hides the tab strip, toolbar, thumbnail rail, notes and footer — the slide
fills the screen with no chrome at all. Nudge the pointer to the top edge to
peek at the toolbar; H or Esc brings everything back. Leaving full screen
drops zen automatically, so you can never get stranded without controls.
N opens a notes panel docked to the right, holding one note per slide.
Notes are saved on the server as JSON — one file per deck under
~/Library/Application Support/SlideView/notes/ — rather than in localStorage,
so clearing a cache cannot take your revision notes with it. Each file records
the deck's name and path, so it still makes sense on its own:
{ "name": "Unit 1",
"path": "…/CN/Unit 1.pptx",
"notes": { "71": "OFC = optical fibre cable…" } }
Writes are debounced and always flushed before the slide or tab changes.
Slides carrying a note are dotted in the thumbnail rail and on the scrubber, and
the deck's note count shows on its library card. ⧉ in the panel header copies
every note in the deck as Markdown; ↑ ↓ jump between annotated slides.
Notes are keyed by page only — editing and re-converting a deck keeps them.
→ ␣ J next · ← ⇧␣ K prev · Home/End first/last · digits jump to a slide
D appearance · F full screen · H zen · T thumbnail rail · + − zoom · 0 fit · W fit width
S star slide · [ ] prev/next starred · ⇧S starred list · N notes
⌘F or / search · ⌘T ⌘W ⌘1-⌘9 tabs · ? help
⌘N new note · ⇧⌘N new drawing · E edit · ⌘S save · ⇧⌘S share · M map
Esc back to library · ⌘R rescan
Reading position, stars and appearance are remembered per deck.
Slide 3 of Unit 1
─────────────────────
Copy Slide as Image
Copy Slide Text
─────────────────────
Search with ▸ Google Lens · Google · Ask Gemini
Share ▸ Share File… · Share as PDF… · Copy File
Open ▸ PDF in Chrome · Original in Default App · Reveal in Finder
─────────────────────
Rename…
Move to Trash
Slide-level entries are hidden for anything not yet rendered — a credentials file or a deck still converting shows only the actions that can actually work.
Chrome cannot be embedded inside another app, and Google Lens cannot reach a
127.0.0.1 URL — so these put the slide (image or text) on the clipboard and
open your real Chrome, where you are already signed in. Press ⌘V there.
The + in the sidebar adds folders; each is scanned recursively and grouped by subfolder. Hover a folder to remove it.
.md files are first-class: they are converted once to a paginated PDF and then
behave exactly like any other deck — thumbnails, filmstrip, text search,
per-slide notes and smart invert all work, and the text stays selectable.
Rendering is deliberately light (dark on white); smart invert darkens it the same way it does a lecture PDF, so notes and slides look consistent side by side.
Supported: headings, paragraphs, bold/italic/strike, inline and fenced
code, blockquotes, ordered/unordered lists, task lists, tables, rules, links and
images (local images are inlined as data URIs, so the render needs no file
access).
The obvious route — NSPrintOperation on a WKWebView — is unusable here, and
both failure modes block the main thread and freeze the whole app:
- with the web view in no window, it spins forever inside
-[NSView canDraw]; - with an offscreen host window, it paginates into ~900 000 pages (a 240 MB PDF).
So instead a script in the page inserts spacers to push any block that would
straddle a page boundary onto the next page, and each page is then captured with
createPDF, which is asynchronous and cannot stall the UI. PDFKit stitches the
slices together. Headings use padding-top rather than margin-top, because
collapsing margins would make offsetTop disagree with where a block actually
paints — and the paginator reads offsetTop.
-
macOS 13+ (Apple Silicon; change
-targetinbuild.shfor Intel) -
Xcode command line tools (
swiftc) -
LibreOffice in
/Applications— only for slide decks (pptx,ppt,odp,key) and spreadsheets (xlsx,xls,ods,numbers) andpages. Everything else renders natively.Word, RTF, ODT and HTML go through Foundation and TextKit instead: drawn via
NSLayoutManagerso embedded images survive, and roughly 30x faster than shelling out (0.24s vs ~8s for a typical document). If Foundation can't parse a file, it falls back to LibreOffice; if LibreOffice is missing, the error says exactly what is needed and why.
Sources/ Swift — HTTP server, library scan, converters (LibreOffice,
Markdown, notebooks, code, images), AppKit shell
web/ UI — index.html, app.css, app.js; draw.html + draw.js for the
drawing board; vendored pdf.js and Excalidraw
Tests/ XCTest suites: Markdown, converters, scan rules, map links, server
Tools/ icon generator, and the script that re-vendors Excalidraw
build.sh compile + assemble the .app
Bug reports, ideas and pull requests are welcome. See CONTRIBUTING.md.
swift test runs the tests; they also run on every push, with a full build of the app.
MIT — see LICENSE. Bundled PDF.js is Apache 2.0 and bundled Excalidraw is MIT; see web/vendor/NOTICE.md.





