Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
- **Editor:** TipTap (ProseMirror)
- **Storage:** Markdown files on desktop (`~/Documents/Set` by default), IndexedDB on the web. Both
use the same file format.
- **Styling:** scoped Svelte CSS and CSS-variable tokens (`src/styles/tokens.css`), light and dark
- **Fonts:** Manrope and Roboto Serif, self-hosted
- **Styling:** scoped Svelte CSS and CSS-variable tokens (`packages/design/tokens.css`), light and
dark
- **Fonts:** Manrope and Roboto Serif, self-hosted (`packages/design/fonts/`)
- **Sync:** [iroh](https://www.iroh.computer/), see [sync.md](sync.md)
- **Dictation:** [whisper.cpp](https://github.com/ggml-org/whisper.cpp) via `whisper-rs`, behind the
optional `dictation` cargo feature
Expand All @@ -26,7 +27,7 @@ One SvelteKit SPA with two backends behind the same `PageStore` interface:
set/
├── src/
│ ├── app.css # global styles entry
│ ├── styles/ # design tokens + shared CSS
│ ├── styles/ # app CSS: controls, popovers, the editor's editing-only rules
│ ├── routes/ # SvelteKit routes (thin: wire state to components)
│ │ ├── +layout.svelte # app shell (+layout.ts loads the workspace)
│ │ ├── +page.ts # `/`, redirects to the last open page or its context
Expand All @@ -46,7 +47,10 @@ set/
│ ├── storage/ # PageStore + backends, Markdown/frontmatter serialization
│ ├── types/ # shared domain types
│ └── utils/ # generic helpers
├── static/ # favicon + fonts
├── packages/ # shared with anything that publishes a note (see below)
│ ├── design/ # tokens, fonts, content.css (how a note looks)
│ └── markdown/ # Set's Markdown dialect + renderHtml for publishing
├── static/ # favicon
├── e2e/ # Playwright suites, run against the built web app
├── scripts/ # release, bundle-check and dev helpers
├── docs/
Expand Down Expand Up @@ -131,6 +135,13 @@ Notes/
- **Set ignores its own writes.** `watch.rs` coalesces events and skips files matching the length
and mtime of Set's last write. If the open page changes outside Set while it has unsaved edits, the
user chooses what to keep.
- **The dialect is shared, the HTML isn't.** `packages/markdown`'s `setDialect` tokenizes Set's
Markdown for both the editor and `renderHtml`. Syntax changes go there, with a test in each. The
editor's renderer rules (the HTML its schema parses, and `data-md` for round-trips) stay in
`editor/markdown/syntax.ts`.
- **A note looks the same everywhere.** How a note's blocks look goes in `packages/design/content.css`,
under `:is(.ProseMirror, .set-content)`, and `renderHtml` emits the DOM the node views draw.
Selection, drag and menu styles stay in `src/styles/editor.css`.
- **The editor doesn't know about pages.** `Editor.svelte` takes a document and emits changes.
Blocks and slash commands are registered in `extensions.ts`.
- **Editor prompts go through state.** Block menus render outside Svelte, so "which page?"
Expand All @@ -143,3 +154,15 @@ Notes/
- [search.md](search.md): indexing and ranking
- [sync.md](sync.md): device sync
- [mcp.md](mcp.md): the MCP server

## Packages

`packages/design` and `packages/markdown` are consumed by the app through the pnpm workspace and
by other sites (the blog) as git dependencies pinned to a release tag:

```json
"@rootstring/set-markdown": "github:rootstring/set#v0.0.3&path:packages/markdown"
```

They ship source (TypeScript and CSS) with no build step, so a consumer's bundler compiles them
(Vite: `ssr.noExternal`). Keep them free of `$lib`, Svelte and DOM dependencies.
2 changes: 1 addition & 1 deletion docs/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ title, up to three names, with `+N` opening the full list (`src/lib/page/Backlin

`wiki_link_titles` (`index.rs`) and `wikiLinkTitles` (`src/lib/search/wiki-links.ts`) share one list
of test cases (`same_titles_as_the_typescript_scanner` / `matches the Rust scanner`). Both follow the
editor's rule (`editor/markdown/syntax-extras.ts`): drop the alias after `|` and the section after
editor's rule (`packages/markdown/src/dialect.ts`): drop the alias after `|` and the section after
`#`, trim, and skip fenced code blocks and backtick spans.

Known gap: indented (four-space) code blocks aren't skipped.
Expand Down
14 changes: 10 additions & 4 deletions e2e/toggle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import { storedBody } from "./db";

/**
* The wait is load bearing: a native caret move reaches ProseMirror on a later `selectionchange`.
* Chrome now and then drops a Home that lands a few milliseconds after a keystroke, so it is
* pressed again until the caret moves.
*/
async function typeAtLineStart(page: Page, text: string) {
await page.keyboard.press("Home");
await expect
.poll(() => page.evaluate(() => window.getSelection()?.anchorOffset ?? -1))
.toBe(0);
await expect(async () => {
await page.keyboard.press("Home");
await expect
.poll(() => page.evaluate(() => window.getSelection()?.anchorOffset ?? -1), {
timeout: 1000,
})
.toBe(0);
}).toPass();
await page.keyboard.type(text);
}

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"check:lockfile": "node scripts/check-lockfile.mjs",
"version:sync": "node scripts/sync-version.mjs",
"version:check": "node scripts/sync-version.mjs --check",
"format": "prettier --write \"src/**/*.{ts,svelte,css}\" \"e2e/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.{ts,svelte,css}\" \"e2e/**/*.ts\"",
"format": "prettier --write \"src/**/*.{ts,svelte,css}\" \"packages/**/*.{ts,css}\" \"e2e/**/*.ts\"",
"format:check": "prettier --check \"src/**/*.{ts,svelte,css}\" \"packages/**/*.{ts,css}\" \"e2e/**/*.ts\"",
"gen-notes": "node scripts/gen-notes.mjs",
"gen-icons": "bash scripts/gen-icons.sh",
"test:unit": "vitest run",
Expand All @@ -29,6 +29,8 @@
},
"license": "AGPL-3.0-only",
"dependencies": {
"@rootstring/set-design": "workspace:*",
"@rootstring/set-markdown": "workspace:*",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.7.3",
"@tauri-apps/plugin-fs": "^2.5.2",
Expand Down
20 changes: 20 additions & 0 deletions packages/design/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# @rootstring/set-design

What makes something look like Set, for the app and for anything that shows a note outside it.

| File | Is |
| ------------- | ------------------------------------------------------------------------------------ |
| `tokens.css` | Colours, type, spacing and the reading column, light and dark (`data-theme` or OS) |
| `fonts.css` | Manrope and Roboto Serif, self-hosted from `fonts/` (OFL, licences alongside) |
| `content.css` | A note's blocks, under `.ProseMirror` (the editor) or `.set-content` (published) |

`content.css` styles the DOM that [`@rootstring/set-markdown`](../markdown)'s `renderHtml` emits.
Import all three, in that order, through a bundler that resolves the relative font URLs:

```css
@import "@rootstring/set-design/fonts.css";
@import "@rootstring/set-design/tokens.css";
@import "@rootstring/set-design/content.css";
```

Equations also need KaTeX's stylesheet (`katex/dist/katex.min.css`).
Loading