- TypeScript 91.3%
- CSS 7.5%
- JavaScript 1.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| build | ||
| electron | ||
| public | ||
| scripts/release | ||
| src | ||
| tests | ||
| .gitignore | ||
| electron-builder.yml | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
| vitest.config.mts | ||
Bläck
Bläck is Swedish for ink (Old Norse blek). A writing studio for long-form fiction: write in scenes and chapters, plan on a corkboard, an outliner, a plot grid and a timeline, keep characters and places on sheets, and compile the book to Word, PDF or EPUB. It runs natively on Windows, macOS and Linux, in English and Swedish.
Internally — the package, executable, app id and settings folder — it's
called black.
Running it
npm install
npm run dev # Vite + Electron with hot reload
Testing
npm test # logic tests, then the app end to end
npm run test:unit # logic tests only, in seconds
npm run test:e2e # builds, then drives the app with Playwright
The end-to-end tests run Electron headless, so they work over SSH and in CI
(.forgejo/workflows/test.yml). See tests/README.md for the layout and how
to write one, and tests/fixtures/ for sample Scrivener, Plottr, Word and
Markdown files to import.
Updates
Bläck looks for a newer release a little after it starts and every six hours,
and offers it in a banner; File → Settings… → Updates chooses between stable
releases, every development build, or no checks. Builds are compared by the
release workflow's run number, which both kinds share. Installing downloads
the file for this computer and: on Windows runs the installer; as an AppImage
replaces itself and restarts; as a deb or rpm opens the system's software
installer; on macOS swaps in the new app and restarts (or shows the download,
if that fails). Copies run from source (npm run dev) don't update.
Releases
Push a version tag and Forgejo builds and publishes the release
(.forgejo/workflows/release.yml):
git tag v0.2.0 && git push origin v0.2.0
Every push to master also makes a development build: the pre-release
called dev, version <package version>-dev.<run>, replaced on each push.
The new one is built beside the old as a draft and only takes its place when
all three platforms are in, so there's always a dev build to download.
Everything builds on the Linux runner: Linux (AppImage, deb, rpm) and Windows
(an NSIS installer, x64 and ARM, with Wine for its metadata) with
electron-builder, and macOS (a zip each for Apple Silicon and Intel) with
scripts/release/build-mac.mjs, which packages with @electron/packager and
gives the app an ad-hoc signature with rcodesign, as Apple Silicon requires.
The packaged Linux app is started and written in before anything is uploaded.
The release stays a draft until all three platforms are in.
The workflow needs a RELEASE_TOKEN secret: a Forgejo access token with
write:repository. The builds aren't signed with Apple or Microsoft
certificates, so macOS and Windows ask before the first start; the release
notes (scripts/release/release-notes.md) say how to get past that.
To build locally: npm run dist:linux or npm run dist:win (needs Wine), or
node scripts/release/build-mac.mjs after npm run build (needs rcodesign
and zip). Dependencies are all bundled into dist/ by Vite, so they live in
devDependencies and no node_modules goes into the packages.
The project format
A project is a folder, not an opaque binary file:
MyNovel.black/
project.json binder tree, metadata, plot grid, timeline, settings
docs/<id>.html one file per document
snapshots/<id>/ timestamped versions
.git/ optional version history
Everything is plain text, so a project can be backed up, synced, diffed or put under version control. Every file is written atomically (write-then-rename), so an interrupted save can never leave a half-written file, and closing the window waits until every pending write has reached the disk. Projects from earlier versions are upgraded when they are opened.
Bläck watches the project folder while it's open. If a sync service (Dropbox, Syncthing, iCloud) or another app changes a file, the change is picked up; if both sides changed the binder at once, it asks which version to keep, and if a document you're editing was also changed elsewhere, the other version is saved as a snapshot rather than lost. Conflicted copies left by sync services are pointed out.
A series bible is a Something.series/ folder holding series.json: the
characters and places shared by every book linked to it.
You can open a project from the command line:
black ~/Books/MyNovel.black
What's in it
Binder — a drag-and-drop tree of folders and documents, with fixed
containers for Manuscript, Characters, Places, Research and Trash. Drop onto the
top or bottom edge of a row to reorder, onto the middle of a folder to nest.
Moves, renames, deletions (even permanent ones), imports and templates can be
undone with Ctrl/Cmd+Z when the cursor isn't in text.
Collections gather documents from anywhere in the binder; a saved search is
a collection that refills itself.
Editor — rich text (bold, italic, underline, strike, headings, quotes,
lists, scene breaks); paragraphs flush left with space between, or with
first-line indents; leading and typewriter scrolling to taste, and a
composition mode that hides everything but the page. A ruler above the
text (View → Ruler) sets the left and right margins: drag a marker, or focus
it and use the arrow keys; double-click the ruler to centre the text again.
Margins are per project and only affect the editor, not compiled output.
Select text and press Ctrl+Alt+M for a margin comment; the footnote
button adds a numbered note. Revision mode colours new text by draft pass
(first revision blue, then red, green, purple, orange), and the colours can be
cleared per document or everywhere. Select a folder and
switch to Text to edit all its scenes as one continuous manuscript.
A split editor keeps a second document — a character sheet,
research, an earlier scene — open beside the one you are writing, and
back/forward retrace the documents you have visited.
Corkboard — index cards for a folder's contents. Edit titles and synopses in place; drag to reorder. Colour cards by label, POV character or status.
Outliner — a spreadsheet of the manuscript: synopsis, status, label, POV, setting, story time, word count and per-document target, all editable inline. Filter by character, place, label or status.
Plot grid — storylines down the side, scenes across the top, beats in the cells. Drag a beat sideways to move it along the timeline, up or down to reassign it to another storyline. Beats with no scene yet sit in an "Unplaced" column. Story templates (Three-Act, Save the Cat!, Hero's Journey, Romancing the Beat, Seven-Point, Mystery) lay out beats for you, and can rough out a scene for each one.
Timeline — scenes in story time rather than reading order, in lanes by POV character, storyline or place. Give a scene a day number ("3"), a date ("1888-11-09") or a date and time; scenes told out of chronological order are flagged, and dragging a card to another column moves it in time. Each card shows the plot-grid beats that land in its scene.
Relationship map — characters (and places) on a canvas, joined by labelled links: drag from a card's + to another card to connect them.
Characters and places — every scene links to a POV character, a cast and a setting, so each sheet lists the scenes it appears in. Sheet fields come from editable templates. A series bible shares characters and places between books: edit a shared sheet in one book and the others pick it up. Check names finds near-misses of character and place names in the manuscript ("Elizabth", "Darcey") and fixes them in one go.
Inspector — synopsis, label, status, POV, cast, setting, story time, keywords, compile inclusion and word targets per document; sheets for characters and places; comments; and history.
History and backups — snapshots of a single document, taken on demand and
automatically on the first edit of each day; rolling back snapshots the current
text first, so nothing is lost. Compare any snapshot or version with the
current text: removed words struck through, added words highlighted. Optionally,
a git history of the whole project, committed as you write (requires git),
and optionally pushed to a backup repository after every save to it.
The defaults for every project on this
computer are in File → Settings… (Ctrl+,; on macOS in the Bläck menu):
whether projects keep a history, and one repository for all of them — each
project gets a branch of its own, named after the book. Each project can go
its own way in Project → History & Backups: always or never keep a
history, and push to the shared repository, a repository of its own (branch
main), or nowhere. Git signs in with your SSH keys or saved credentials and is
never allowed to stop and ask, and a push still under way when you quit is
given up to 30 seconds to finish. Closing a project also writes a zip backup
to a folder of your choice, keeping the newest ten. While you write in
Settings sets how often a copy is kept while writing — every 10 minutes by
default, from every 5 minutes to every 2 hours, or only on close: each time, a
project with a history gets a commit (and a push), and a zip backup too if
switched on under Backups. Either happens only when something has changed.
Themes — Bläck's own dark and light, Darcula, Everforest, Gruvbox, Nord,
Solarized, Dracula, Catppuccin, Tokyo Night and Rosé Pine (in dark and light where
they come in both), and Typewriter: ink on a sheet of paper with a faint grain,
in Courier Prime (by Alan Dague-Greene, SIL Open Font License; see
src/assets/fonts/CourierPrime-OFL.txt). Chosen in Settings for every project, or set to follow the
system's light and dark mode; Ctrl/Cmd+Shift+T switches a theme between its
light and dark. Settings → Writing sets how the text looks as you write, the
same in every project: the typeface (the theme's own, Bläck's serif, sans or mono,
or any font on the computer), the interface's typeface, text size, line width,
line spacing and typewriter scrolling. How a book's paragraphs are set when
compiled stays with the book, in Project Settings.
Your own themes — a theme is a JSON file in Bläck's themes folder:
| System | Folder |
|---|---|
| Linux | ~/.local/share/black/themes (or $XDG_DATA_HOME/black/themes) |
| macOS | ~/Library/Application Support/black/themes |
| Windows | %APPDATA%\black\themes |
Settings opens the folder, and copies any theme there to start from. A file saved there shows at once, so a theme can be tuned with Bläck open beside it.
{
"name": "Harbour",
"scheme": "dark",
"pair": "light",
"colors": { "bg": "#10151c", "panel": "#161d26", "paper": "#121820", "accent": "#e0a458" },
"fonts": { "prose": "Literata, Georgia, serif", "headings": "Fraunces, serif" }
}
scheme is "dark" or "light"; any colour left out comes from Bläck's own
dark or light. pair is the theme Ctrl/Cmd+Shift+T switches to (a built-in
theme's id such as everforest-light, or user- and another file's name).
Colours are hex (#1a2b3c, with or without alpha) or rgb()/hsl(). A file
with a mistake is listed in Settings with what's wrong. The colours:
| Colour | For |
|---|---|
bg |
Behind everything: the window itself |
bg-sunken |
Wells and the ruler, a step further back |
panel |
The binder and inspector |
panel-2 |
A second shade of panel, for headers and stripes |
elevated |
Menus, dialogs and popovers |
hover |
Under the pointer |
active |
Pressed or selected, quietly |
border |
Lines between things |
border-soft |
Fainter lines |
border-strong |
Lines that need to show |
text |
Text |
text-2 |
Secondary text |
muted |
Labels and hints |
faint |
The faintest text: placeholders, counts |
accent |
Buttons, selection, the caret |
accent-hover |
Accent under the pointer |
accent-ink |
Text on the accent |
accent-soft |
A tint of the accent, behind selected rows |
accent-line |
Accent-coloured lines |
danger |
Deleting and errors |
danger-soft |
A tint of danger |
success |
Done and saved |
info |
Information |
paper |
The page the prose sits on |
paper-text |
The prose |
comment |
Commented text |
comment-active |
The comment being read |
Compile — assemble the manuscript into Word (.docx), PDF, EPUB, Markdown,
styled HTML or plain text, with optional title page, chapter headings,
synopses, page breaks and a configurable scene separator. Standard manuscript
format produces a submission-ready file: 12pt Times or Courier,
double-spaced, one-inch margins, a contact block and rounded word count on the
title page, and a "Surname / TITLE / page" running header. Presets save a
set of choices (Submission manuscript, E-book, Print proof, Beta readers, or
your own), chapter headings can be numbered ("Chapter {N}" → "Chapter
Seven", "{R}" → "VII"), front and back matter folders print before and
after the book, and footnotes become real Word footnotes, linked EPUB notes,
or [^n] in Markdown.
Import from Scrivener — a Scrivener 2 or 3 project (choose the .scrivx
inside the .scriv folder) into a new project or the open one: binder
structure, text with its formatting, synopses, notes, labels, statuses and
keywords. Folders named Characters or Places become sheets. Added to an open
project, the draft and research land in "From Scrivener" folders and labels
and statuses are matched by name.
Import from Plottr — a .pltr file into a new project or the open one:
chapters become scenes (beat hierarchies become folders), plotlines become
storylines, cards become beats with the scene's cast, setting and tags, and
characters and places become sheets with their custom attributes.
Import documents — Word (.docx), Markdown, plain text, HTML and RTF files into any folder, optionally split into documents at chapter headings and scene breaks.
Search and replace — full-text search across titles, synopses, notes and every document, with case, whole-word and regular-expression matching, and replace-all that snapshots each document before changing it. A fuzzy quick-open jumps by title.
Spelling — a per-project dictionary (right-click a word to add it); character and place names are always accepted.
Targets and statistics — manuscript and session word goals with live progress, a deadline with the daily word count it needs and a projection from your recent pace, a 30-day writing history with streaks, word counts by status, scene and chapter tallies, and reading time. Writing sprints from the status bar (10–45 minutes) count the words you write and keep a record of your pace.
Languages
Bläck follows the system language when it has it, else English; choose
another in File → Settings…, or on the welcome screen. The setting is
per computer. Translations live in src/i18n/locales/<code>/, one file per
area of the app; see src/i18n/README.md to add a language. Anything a
language hasn't translated yet shows in English.
The interface is translated; what you write isn't touched. New projects get their starting labels, statuses and sheet fields in the current language, and the root folders (Manuscript, Characters…) always show in it. Compiled manuscripts keep the English submission conventions ("END", "about N words").
Keyboard
Ctrl/Cmd+N |
New text |
Ctrl/Cmd+Shift+F |
New folder |
Ctrl/Cmd+O |
Open project |
Ctrl/Cmd+S |
Save now (it also autosaves) |
Ctrl/Cmd+Shift+S |
Take snapshot |
Ctrl/Cmd+E |
Compile |
Ctrl/Cmd+P |
Quick open |
Ctrl/Cmd+Shift+P |
Find in project |
Ctrl/Cmd+Shift+H |
Find and replace in project |
Ctrl/Cmd+1…6 |
Editor / Corkboard / Outliner / Plot / Timeline / Relationship map |
Ctrl/Cmd+Z |
Undo — text in the editor, otherwise the last binder change |
Alt+← / Alt+→ |
Back / forward |
Ctrl/Cmd+\ |
Split editor |
Ctrl/Cmd+Alt+M |
Comment on selection |
Ctrl/Cmd+B |
Toggle binder |
Ctrl/Cmd+I |
Toggle inspector |
F11 |
Composition mode |
Ctrl/Cmd+Shift+T |
Switch the theme between its light and dark |
Architecture
- Main process (
electron/) owns the filesystem.project.tsis the whole storage layer;main.tsholds the window, the menu and the IPC surface. - Preload (
electron/preload.ts) exposes one explicit, enumerated API overcontextBridge. The renderer has nofs, norequire, no node integration, and the page runs under a strict CSP. - Renderer (
src/) is React + Zustand. One store holds the project manifest, the loaded document bodies and the UI state. Manifest edits are debounced to an atomic write; document bodies are written per document on a shorter debounce.