sdocs-dev 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/public/default.md CHANGED
@@ -1,36 +1,85 @@
1
1
  ---
2
2
  styles:
3
- fontFamily: "Inter"
3
+ fontFamily: Inter
4
4
  baseFontSize: 16
5
5
  lineHeight: 1.75
6
- headers: { fontFamily: "inherit", scale: 1, marginBottom: 0.4 }
7
6
  h1: { fontSize: 2.1, fontWeight: 700 }
8
7
  h2: { fontSize: 1.55, fontWeight: 600 }
9
8
  h3: { fontSize: 1.2, fontWeight: 600 }
10
- h4: { fontSize: 1, fontWeight: 600 }
11
- p: { lineHeight: 1.75, marginBottom: 1.1 }
12
- link: { color: "#2563eb", decoration: "underline" }
13
- code: { font: "JetBrains Mono", background: "#f4f1ed", color: "#6b21a8" }
14
- blockquote: { borderColor: "#2563eb", borderWidth: 3, fontSize: 1, color: "#6b6560" }
15
- list: { spacing: 0.3, indent: 1.6 }
9
+ p: { lineHeight: 1.75, marginBottom: 1 }
10
+ light:
11
+ background: "#ffffff"
12
+ color: "#1c1917"
13
+ dark:
14
+ background: "#2c2a26"
15
+ color: "#e7e5e2"
16
16
  ---
17
+
17
18
  # Say hello to SmallDocs: A markdown-first replacement for Word & GDocs
18
19
 
19
20
  If you're working with agents, a document written in markdown is <ins>officially</ins>* 407 times more useful than a document locked inside a `.docx` or `.gdoc` file format. Because of this, I believe Word and GDocs' days are numbered. (*I am the official.)
20
21
 
21
22
  But while markdown is great for agents, it's a bit annoying for humans. Quickly and elegantly reading a `.md` file requires you to open your code editor and enter "preview" mode. Sharing a markdown file requires you to actually send the file to someone. They then have to download it and find the least annoying way to read it.
22
23
 
23
- SmallDocs is an [open source](https://github.com/JoshInLisbon/SDocs) attempt at something different. It lets you (or your agent) easily, elegantly and privately **read**, **share**, **format** and **export** `.md` files.
24
+ SmallDocs is an [open source](https://github.com/JoshInLisbon/SDocs) attempt at something different. It lets you (or your agent) easily, elegantly and <ins>100% privately</ins> **read**, **format**, **share** and **export** `.md` files.
25
+
26
+ Reading a `.md` file in SmallDocs feels just like this (you're reading markdown right now). And by playing with the styles, it can feel like [this](https://sdocs.dev/#md=jZTdbuNGDIXv9RQHykWBrS1Ijp04LlAgSRF021wEmwC5piRKM_BoqJ0ZxdAWBfoQfcI-STFSfnc3Re8MknN4-JHycrlMgg6Gd0jvFOPcBUiDWyMHfGKqtW3TxIfRsN8lQCM2XFGnzbhDei2O0gQoyfOV2HCrv_AOxTYBjLb8K-tWhR2K7CwBFFPNzu_wx1uRG0NjQ9rhF-17Q2O6gK8o-imyYoGOXKvthYQg3Q55tsGfUax40pl7rrL1HF-9jRfZ6RTvY_itVJEdTymj7T5ma67EUdBid0itWE6ndGmk2n8eJHAsKsXV7O51HdQO68WrTnl2tnnUi0MnQORS7Vsng613SI8aak6aPJ0ylRhxMbiqVutiOwdfEXrOb8v1pjierbx4fU5Tvlmt6qf097xePpXWazo9oXTxtasNN3ySLl40T8oNrflJs5J6Vnv7KudtXb1-dUrrZlXNr2py--8RKLigYv0VAc7rTbV-n8Cj7_cI8LZab-l_EdiWJ2fF-hsCK1qtiu3rWcotbc_y_ybw7asXo8vlMkmO8M7nlCQfLQgHcaaG9EF3-gvXaMTB73XXadsuEBQ7hvYgfB40Bzgu2ZjpOt2sA2_kYMYsSY6OcK9G3PbMNX7XxnhcStc7Vmy9FpskPyN97I5mcFZ7xT72QKdtDbFmxEEHhY4CO03GR9N7KwfDdcs_QYfoJSht91EjKAroaM8eh_jzwJMpyOB8luKfv_7Gb6IsrqXac5JEEPTAjlpGz86Lnco9Vps8cqh9DEcvQ-BsnmP5NGUVRdmjd9JpzygWeZ7_mOFiiFA8k6sUKrFe-8A2mBFeycHPHksexdY4yXMc-i4u6xUW1E56jyCwTA5f2MkM8045Ztw4qoKu2MfYEYoMn3hyNYHryVHrqFdJcsGNOEYnD9Gv2MUMQwdQS9pm0yF4rqKTnryH4weOjCd2UazRzgeIjfvwnutsbrnKcO7agefdxEIaghKXJB8bjDKA2mh0yvIDuzEuqF3E1A-OYSU830pcyWOYSi-u1LbNcDN4Nd11hnO_x4eDGj889j7OcGnE89S2FNnPayzZv4gq6nu2HtQEdpMhH6TPcM3zWLpm8vAcguEM906HyZSazlAcyELJ4Gbo57jSlgzulAytCnO7hnyIHQ80xj0NNv49BLI1vHQ8K-lpha1-4Ig86I6z5F8&theme=light), [this](https://sdocs.dev/#md=jVTtbtpIFP0_T3Fl1D8RNh4DDvaPlfJBdiO1aupktdKqUhg81zDFnqEzYxJaKu1D7BP2Saqx8YbQZlsLieGce-85Plzb931ihS0xBe_s5hoyLFCjzBE2kUeM3ZZoUgJQKGmvWCXKbQrerap1jnDLpIGhRwDmzOCVkvZWfMIU6JgAlELiHygWS5sCDWIHLZFx1CaFz8_HZWqurPL6UDG9EPJcWauqFMJgCF9cG-062vFRi0bPURqMWnx4jNNxQ6wdfqyQNFSuOHZdKXhXQjN4o6TyGnZeqnz1sVa2qZkrzVH_JbhdphD1D5S6aaUw1lWaNcuFXDiCjvsgJEfZhBHty1w2BMDFl68WWtWSp-D1iubyGiZXpdIOjEZREu3Bgxz_4ymjGI1aw034q2d0mMQJZx3d3e-RblxMCub1n7qS8XAShl3Xj3K4OBLo_3RmPI75adzO5EyvfpRAyCmlp0cJ5AmnPHk5gSIs4iJ_MYHxhMVF8f8J0JjOo-jQbVGweHz6SwnsBY4T-H7mZJ6MEnQzfd8npAfHzx0h58wg_Jm9TmG2tHZt0sGArUWAj6xalxjkqhpsohkhvR6c1XaJ0oqcWaEkIWdlCRo_1misaQ5CIzA4R6ZRg1UrlCAk2CXCzPUqLT41rbN9qikhs9mMPOPSrt-s7kuxwfsgCJoq52Aq-VoJaY371YObt7d3MOAqrytswAuNzDoPEh-gw4NG5YNRknwmAF7zDvJS8N7VTFvU5RYyXCttvb6jC6UrZh1fMb3i6kG2eK6kRdkQPXhHIUNTl9a8l-9lhhuUNcJC4wNEw1dBEHjkS-v65CRDs1bS4MkJfP3nX5hFIYXWKJ8dWRPcjecqv588Rquk2Cu3xfetqyiMYj8c-jS5o2Eaus_fbV2tS1fw8t_4FNXgQKNz6hL9fXoY6CAVnJAMrRa4cbF2DMy3cH0ZEPIbZC7wUlTCvW_C8Gkh1qihErK22Bzd5q1wG7Q6l9PX07vpd1I3qCsmUdpyCxxLtGgOVAO4WwoDLHd7AjmTUlmYI9SSK4lBuyFaKw0XiqMhZNccYAdvkEkhF7AjO7-59l_uRHYwCkPYwbXcsFLw7g5grvgWWpq6GcIYN0NpEPvKdsXbkhHs4LKLxxkr3GPZklECu4OgAB9zRI6O_QY&theme=dark) or even [this](https://sdocs.dev/#md=jVRLbtxGEN3zFA-jhQFBQ8yM9Z1FANmAEBt2spAAxcsiu0h2ptlFdxc1mQQBcohcwuscwTfJSYJuzsSSP0GWrGpW1ftUzefzQq06XmN2z7xhb2A93thYiZ8VUXeO47oAGvF6Q711uzVmP4zeqswKoKLIN-L11v7KaywvCsBZz9-zbTtdY1lenhVAx2Q4xDV-e1rntURurMct-Tg7QawpDbIsF2cn6Cm01r8QVenXWJSnZ_g9lVoeqkwtV-X5FF89jS_38SGFn9ZalqucqpzUm_ejKKc3lQTD4d4a7dY4O3lUalFeTb1dBlUACXe9aYOM3qwxO2qapmrOZjlTi5OQgiuzulqdT8FHBPybrxfPr1bVLFfOrG2epFdXl4vq6pD-2qwvD0_54rR-Xs9OPp_KNKtmMTv5VPOiOa_OF4eatZip2tO_LpsFXz7-6_GghsLmawwsaXm-PP2MAb7khbn8NgP7ub_FwJkhw6v_xcB-xM8ZWNFqtXrCQLWgxdXlfzPw5V-fBp3P50VxhC9WpSjuusAMQ7tY4geBVes5UNiVeD1GBaG2uoN2pAi8pWAituQNB-vbsiiOjnATrKEd_v7jT1y7hnoqinsGhWAfOLfSjuFIGdQoBy_iQd5gS27DBlEDJYPCepX8VpzhqPBpGSsJnYgpcddxesqsERQYFQV2O2ytYbCXse3QSIBuBQPL4LjEGxq9CTt05NuIinXL7FGN1hnr21jihoygH6OtYYJtNEJGhTSQgT2MSIhlUXyHdzLCiH-meLDR6h5mmeMtK5zEND6snmRkia1nMWMZxHotJ0aU0QbrXAJNwVjPEZQoHhzVjK3VhGEMUKpcynkDL-jZjxN-2XoOqEJCq9h2pPzAAVuKaALHbtLjlnQMB0VesPv4oU8yM3oJ3voWUSkom6nh8fFAUT9-sBGG4Unp-DhNlYZvqJcxoqINJ0O843iS4-9HHjm3deLbfcJqjmwlaAebMKeerzl8_MvbXiLeiqeoHHawEVTFMRi3Q8U0qm1Gl8dNeHaIY2gSIzWFvYXekh85XWgYVrKuxD0jDuw1K97JGDLfgTNrtYzOoKOH5Bnascm8ZnqOJieNPrJmWfY2pAQlo0u-Ddh78U5CSE33RCZm2uRPn3tOsuT7Ch2DZzMh0C7xvKXQ53GiNFriFVRkg9MLDJ2oxBI_-onFNjl80m70B-Xe_IQbqlXCriiuUYt_4Kya9WaMGiw51NIPjn9BMzoHadZFMccLkU3sZIiTvFX6TCTUCWTjRDK0mq2zvi3muEYQaVSGtFGQBw5OZGP3XFTBmpaLOW7z6oGCQvxeJnog65JVD4IVc7yUpmGe7gXVOpJzO2jS3cDZDaPODzLYxN1dZuq6Sot3OEmv0vJ4UWjY5UEEth8Cx4idjCVy_ud0nY4rtr49frJ1aS3Q04ZjsmRPrWe19aRUHca-crmmdcnfNZmDqW0_SIw2wemsc_vopC0pzoc-y-LFczoRVpON6zGQsnk80-OL2dEwsI8JQcWfrF4WxatnzuUY1Zuy-Ac&theme=light):
27
+
28
+ ![Examples](https://sdocs.dev/public/images/examples.png)
24
29
 
25
- Reading a `.md` file in SmallDocs feels just like this (you're reading markdown right now). And creating a SmallDoc for a `.md` file (+ automatically opening your browser to read it) is as simple as:
30
+ Use SmallDoc's CLI for speed. Creating a SmallDoc for a `.md` file (+ automatically opening your browser to read it) is as simple as:
26
31
 
27
32
  ```
28
- # npm i sdocs-dev
33
+ # npm i -g sdocs-dev
29
34
  sdoc README.md
30
35
  ```
31
36
 
32
37
  ## How SmallDocs work
33
38
 
39
+ ### URLs
40
+
41
+ The URL format for SmallDocs is:
42
+
43
+ ```
44
+ https://sdocs.dev/#md={compressed & encoded .md}
45
+ ```
46
+
47
+ Your entire document (content and styles) lives in the URL hash.
48
+
49
+ To keep URLs as short as possible, SmallDocs compresses your markdown using [deflate](https://en.wikipedia.org/wiki/Deflate) (a standard compression algorithm built into every browser) and then encodes the result with [base64url](https://en.wikipedia.org/wiki/Base64#URL_applications) (a URL-safe variant of base64 that avoids characters like `+`, `/`, and `=` which would otherwise need percent-encoding). Style properties that match built-in defaults (e.g. `fontFamily: Inter`, `baseFontSize: 16`) are omitted from the URL — only values that differ from defaults are included, which keeps URLs shorter.
50
+
51
+ The `mode` parameter controls which view opens. Valid values are `read` (clean reading view, style panel hidden), `style` (style panel visible), and `raw` (raw markdown editor). When sharing a link for someone to read, use `mode=read`:
52
+
53
+ ```
54
+ https://sdocs.dev/#md=...&mode=read
55
+ ```
56
+
57
+ You can also link directly to a section using the `sec` parameter. Click any heading's link icon to copy its section URL:
58
+
59
+ ```
60
+ https://sdocs.dev/#md=...&sec=url-formatting
61
+ ```
62
+
63
+ The `sec` value is the heading text slugified (lowercased, spaces become hyphens, special characters stripped). The page will scroll to that section on load.
64
+
65
+ The `theme` parameter forces a specific theme: `theme=light` or `theme=dark`. This is useful when sharing a link where the document looks best in a particular theme. The override is view-only — it applies for that view but won't change the reader's saved theme preference.
66
+
67
+ ### Privacy
68
+
69
+ Because the SmallDocs url format is:
70
+
71
+ ```
72
+ https://sdocs.dev/#md={compressed & encoded .md}
73
+ ```
74
+
75
+ Your document never hits the SDocs server.
76
+
77
+ This layer of privacy is built into how HTTP works. The hash fragment (everything after the `#` in a URL) is never sent to the server by the browser. It always stays entirely client-side:
78
+
79
+ > "The fragment is not sent to the server when the URI is requested; it is processed by the client" - [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment)
80
+
81
+ The [sdocs.dev](https://sdocs.dev) site is purely a rendering space. JavaScript reads `window.location.hash`, decompresses and decodes the content, and renders your `.md` locally. The server is about 60 lines of Node.js that serves static files — no database, no logging, no analytics.
82
+
34
83
  ### Formatting
35
84
 
36
85
  SDocs adds basic styling to markdown files. You write your content in regular markdown and the styles live in a metadata block at the top of the file.
@@ -53,78 +102,70 @@ styles:
53
102
  baseFontSize: 17
54
103
  h1: { fontSize: 2.3, fontWeight: 700 }
55
104
  p: { lineHeight: 1.9, marginBottom: 1.2 }
56
- light:
57
- background: "#fffaf5"
58
- color: "#1a1a2e"
59
- h1: { color: "#c0392b" }
60
- dark:
61
- background: "#1a1520"
62
- color: "#e7e5e2"
63
- h1: { color: "#ef6f5e" }
105
+ ...
64
106
  ---
65
107
  ```
66
108
 
67
- Non-color properties (fonts, sizes, spacing) are shared across themes and live at the top level. Colors live inside `light:` and `dark:` blocks so both themes render correctly.
68
-
69
- All color controls are in the **Colors** section of the style panel. The light/dark toggle at the top of that section lets you customize each theme independently. Colors cascade from general to specific — set `color` once and it flows to headings, paragraphs, and lists unless you override them individually.
70
-
71
109
  (Click "**Raw**" — top left — to see the front matter for this file. See all available properties [here](https://sdocs.dev) or by running `npm i sdocs-dev; sdoc schema`.)
72
110
 
73
111
  When a `Styled .md` file is rendered in the SmallDocs interface the specified styles are applied. If a plain `.md` file is rendered the default styles are applied.
74
112
 
75
- ### URLs
113
+ #### Light & dark modes
76
114
 
77
- The URL format for SmallDocs is:
115
+ You can nest styles in `light` and `dark` keys:
78
116
 
79
117
  ```
80
- https://sdocs.dev/#md={compressed & encoded .md}
118
+ light:
119
+ background: "#fffaf5"
120
+ color: "#1a1a2e"
121
+ h1: { color: "#c0392b" }
122
+ dark:
123
+ background: "#1a1520"
124
+ color: "#e7e5e2"
125
+ h1: { color: "#ef6f5e" }
81
126
  ```
82
127
 
83
- Your entire document (content and styles) lives in the URL hash.
84
-
85
- To keep URLs as short as possible, SmallDocs compresses your markdown using [deflate](https://en.wikipedia.org/wiki/Deflate) (a standard compression algorithm built into every browser) and then encodes the result with [base64url](https://en.wikipedia.org/wiki/Base64#URL_applications) (a URL-safe variant of base64 that avoids characters like `+`, `/`, and `=` which would otherwise need percent-encoding).
128
+ These will be used when you view the site in each mode.
86
129
 
87
- The `mode` parameter controls which view opens. Valid values are `read` (clean reading view, style panel hidden), `style` (style panel visible), and `raw` (raw markdown editor). When sharing a link for someone to read, use `mode=read`:
130
+ Non-color properties (fonts, sizes, spacing) are shared across themes and live at the top level.
88
131
 
89
- ```
90
- https://sdocs.dev/#md=...&mode=read
91
- ```
132
+ All color controls are in the **Colors** section of the style panel. The light/dark toggle at the top of that section lets you customize each theme independently. Colors cascade from general to specific — set `color` once and it flows to headings, paragraphs, and lists unless you override them individually.
92
133
 
93
- You can also link directly to a section using the `sec` parameter. Click any heading's link icon to copy its section URL:
134
+ ### Drag & drop
94
135
 
95
- ```
96
- https://sdocs.dev/#md=...&sec=url-formatting
97
- ```
136
+ Drag any `.md` file onto the editor to SmallDoc it instantly.
98
137
 
99
- The `sec` value is the heading text slugified (lowercased, spaces become hyphens, special characters stripped). The page will scroll to that section on load.
138
+ ### Exports
100
139
 
101
- ### Privacy
140
+ SmallDocs can export your document in four formats:
102
141
 
103
- Your document never hits the SDocs server. This layer of privacy is built into how HTTP works. The hash fragment (everything after the `#` in a URL) is never sent to the server by the browser. It always stays entirely client-side:
142
+ - **Raw .md** your markdown content with all front matter stripped. Plain markdown, compatible with anything.
143
+ - **PDF** — a styled PDF generated from the rendered view via the browser's print engine.
144
+ - **Word (.docx)** — a styled Word document generated from the rendered HTML.
145
+ - **Styled .md** — your markdown with the `styles:` front matter block included. This is the format SmallDocs reads back in, so your formatting is preserved.
104
146
 
105
- > "The fragment is not sent to the server when the URI is requested; it is processed by the client" - [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment)
147
+ ### Collapsed headers
106
148
 
107
- The [sdocs.dev](https://sdocs.dev) site is purely a rendering space. JavaScript reads `window.location.hash`, decompresses and decodes the content, and renders your `.md` locally. The server is about 60 lines of Node.js that serves static files — no database, no logging, no analytics.
149
+ SmallDocs loads with all headers collapsed. This is done because it makes it easy to get an overview of the whole document.
108
150
 
109
- ### Auto-save
151
+ If you expand a parent, all of its children expand too.
110
152
 
111
- Because the URL includes your full document and dynamically updates via JavaScript, every change you make is instantly preserved in the URL. This works when you're offline.
153
+ ### Copy & paste
112
154
 
113
- ### Drag & drop
155
+ Every header has its own copy and paste button. This copies its content and all of its children's content. At the moment this is the fastest way to get SmallDoc content into your agent's context, but we're looking for novel ideas to make this better.
114
156
 
115
- Drag any `.md` file onto the editor to SmallDoc it instantly.
157
+ ### Works offline
116
158
 
117
- ### Exports
159
+ `https://sdocs.dev` uses extensive client side caching. If you've loaded the site once, you can visit it even when you're offline. If something has changed server side, we invalidate the cache and the next time you visit the site you'll get the latest version.
118
160
 
119
- SmallDocs can export your document in four formats:
161
+ ### Auto-save
120
162
 
121
- - **Raw .md** your markdown content with all front matter stripped. Plain markdown, compatible with anything.
122
- - **PDF** — a styled PDF generated from the rendered view via the browser's print engine.
123
- - **Word (.docx)** — a styled Word document generated from the rendered HTML.
124
- - **Styled .md** — your markdown with the `styles:` front matter block included. This is the format SmallDocs reads back in, so your formatting is preserved.
163
+ Because the URL includes your full document and dynamically updates via JavaScript, every change you make is instantly preserved in the URL. This works when you're offline.
125
164
 
126
165
  ## The CLI
127
166
 
167
+ ### Installation
168
+
128
169
  SmallDocs has a command-line tool that lets you open, share, and style markdown files from the terminal. Install it once:
129
170
 
130
171
  ```
@@ -141,28 +182,47 @@ sdoc README.md
141
182
 
142
183
  Your browser opens with the document styled and readable. That's it — one command to go from `.md` file to formatted document.
143
184
 
144
- ### Modes
185
+ ### Share a link
145
186
 
146
- By default, files open in read mode. You can open in any mode:
187
+ ```
188
+ sdoc share README.md
189
+ ```
190
+
191
+ This copies a shareable link to your clipboard.
192
+
193
+ You can also combine it with options:
147
194
 
148
195
  ```
149
- sdoc README.md # read mode (default)
150
- sdoc README.md --write # write mode (contentEditable editor)
151
- sdoc README.md --style # style mode (styling panel visible)
152
- sdoc README.md --raw # raw mode (plain markdown source)
196
+ sdoc share report.md --section "Results" # deep-link to a heading
197
+ sdoc share notes.md --write # link opens in write mode
198
+ sdoc share notes.md --dark # link opens in dark theme
153
199
  ```
154
200
 
155
- ### Share a link
201
+ ### Start a new document
156
202
 
157
203
  ```
158
- sdoc share README.md
204
+ sdoc new
205
+ ```
206
+
207
+ Opens a blank document in write mode, ready to type a `h1`.
208
+
209
+ ### Style schema
210
+
211
+ ```
212
+ sdoc schema
159
213
  ```
160
214
 
161
- This copies a shareable link to your clipboard no browser opens, no URL printed. The entire document is compressed into the URL hash, so there's nothing to host or upload. You can also combine it with options:
215
+ Prints every available style property with its type, default value, and description. This is designed to be readable by both humans and LLMs so your agent can write YAML front matter for you.
216
+
217
+ ### Modes
218
+
219
+ By default, files open in read mode. You can open in any mode:
162
220
 
163
221
  ```
164
- sdoc share report.md --section "Results" # deep-link to a heading
165
- sdoc share notes.md --write # link opens in write mode
222
+ sdoc README.md # read mode (default)
223
+ sdoc README.md --write # write mode (contentEditable editor)
224
+ sdoc README.md --style # style mode (styling panel visible)
225
+ sdoc README.md --raw # raw mode (plain markdown source)
166
226
  ```
167
227
 
168
228
  ### Pipe from stdin
@@ -175,14 +235,6 @@ cat notes.md | sdoc share # pipe to clipboard link
175
235
  your-agent --output md | sdoc # pipe agent output to browser
176
236
  ```
177
237
 
178
- ### Start a new document
179
-
180
- ```
181
- sdoc new
182
- ```
183
-
184
- Opens a blank document in write mode, ready to type.
185
-
186
238
  ### Default styles
187
239
 
188
240
  If you find a style you like, use the "Save as Default" panel in the Style view to generate a command that saves your preferences to `~/.sdocs/styles.yaml`. The CLI automatically applies these defaults to every file you open — unless the file has its own styles, which always take priority.
@@ -192,14 +244,6 @@ sdoc defaults # view your current defaults
192
244
  sdoc defaults --reset # remove them
193
245
  ```
194
246
 
195
- ### Style schema
196
-
197
- ```
198
- sdoc schema
199
- ```
200
-
201
- Prints every available style property with its type, default value, and description. This is designed to be readable by both humans and LLMs — so your agent can write YAML front matter for you.
202
-
203
247
  ### For agents
204
248
 
205
249
  The CLI is designed to work well in automated workflows. A few patterns:
@@ -209,18 +253,3 @@ The CLI is designed to work well in automated workflows. A few patterns:
209
253
  - **Deep-link to context**: `sdoc share file.md --section "Heading"` creates a URL that scrolls straight to the relevant section
210
254
  - **No auth, no API keys**: everything is client-side — the URL *is* the document
211
255
 
212
- ### Small opinionated things
213
-
214
- SmallDocs has opinions. We do some things which might not work for everyone but hopefully make the general `.md` experience better for most.
215
-
216
- We welcome your opinions. Raise an issue on GitHub or make a pull request if you want something to change.
217
-
218
- #### Collapsed headers
219
-
220
- SmallDocs loads with all headers collapsed. This is done because it makes it easy to get an overview of the whole document.
221
-
222
- If you expand a parent, all of its children expand too.
223
-
224
- #### Copy & paste
225
-
226
- Every header has its own copy and paste button. This copies its content and all of its children's content. At the moment this is the fastest way to get SmallDoc content into your agent's context, but we're looking for novel ideas to make this better.
Binary file
Binary file
Binary file
Binary file
package/public/index.html CHANGED
@@ -4,9 +4,6 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>SDocs</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
10
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32' fill='none'%3E%3Crect x='2' y='2' width='28' height='28' rx='7' fill='%232563EB' fill-opacity='.15'/%3E%3Crect x='2' y='2' width='28' height='28' rx='7' stroke='%232563EB' stroke-width='2'/%3E%3Cpath d='M9 11h14M9 16h11M9 21h8' stroke='%232563EB' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E">
11
8
  <link rel="stylesheet" href="/public/css/tokens.css">
12
9
  <link rel="stylesheet" href="/public/css/layout.css">
@@ -37,12 +34,12 @@
37
34
  <button class="btn active" id="btn-read" title="Read">
38
35
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 7v14"/><path d="M16 12h2"/><path d="M16 8h2"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/><path d="M6 12h2"/><path d="M6 8h2"/></svg>
39
36
  </button>
40
- <button class="btn" id="btn-write" title="Write">
41
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
42
- </button>
43
37
  <button class="btn" id="btn-style" title="Style">
44
38
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m14.622 17.897-10.68-2.913"/><path d="M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z"/><path d="M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15"/></svg>
45
39
  </button>
40
+ <button class="btn" id="btn-write" title="Write">
41
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>
42
+ </button>
46
43
  <button class="btn" id="btn-raw" title="Raw">
47
44
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
48
45
  </button>
@@ -245,6 +242,10 @@
245
242
  <label>Border</label>
246
243
  <div class="color-pair"><input type="color" id="ctrl-bq-border-color" value="#2563eb"><button class="reset-btn" id="reset-bq-border-color" title="Reset to default">↺</button></div>
247
244
  </div>
245
+ <div class="control-row">
246
+ <label>Background</label>
247
+ <div class="color-pair"><input type="color" id="ctrl-bq-bg" value="#f7f5f2"><button class="reset-btn" id="reset-bq-bg" title="Reset to default">↺</button></div>
248
+ </div>
248
249
  <div class="control-row">
249
250
  <label>Text</label>
250
251
  <div class="color-pair"><input type="color" id="ctrl-bq-color" value="#6b6560"><button class="reset-btn" id="reset-bq-color" title="Reset to default">↺</button></div>
@@ -573,7 +574,8 @@
573
574
  </span>
574
575
  </div>
575
576
 
576
- <script src="https://cdn.jsdelivr.net/npm/marked@11/marked.min.js"></script>
577
+ <script src="/public/brotli-wasm.js"></script>
578
+ <script src="/public/vendor/marked.min.js"></script>
577
579
  <script src="/public/sdocs-yaml.js"></script>
578
580
  <script src="/public/sdocs-styles.js"></script>
579
581
  <script src="/public/sdocs-state.js"></script>
@@ -582,5 +584,17 @@
582
584
  <script src="/public/sdocs-export.js"></script>
583
585
  <script src="/public/sdocs-write.js"></script>
584
586
  <script src="/public/sdocs-app.js"></script>
587
+ <script>
588
+ var APP_VERSION = '__APP_VERSION__';
589
+ if ('serviceWorker' in navigator) {
590
+ navigator.serviceWorker.register('/sw.js').then(function (reg) {
591
+ var msg = { type: 'check-update', version: APP_VERSION };
592
+ if (reg.active) reg.active.postMessage(msg);
593
+ navigator.serviceWorker.addEventListener('controllerchange', function () {
594
+ if (navigator.serviceWorker.controller) navigator.serviceWorker.controller.postMessage(msg);
595
+ });
596
+ });
597
+ }
598
+ </script>
585
599
  </body>
586
600
  </html>
@@ -197,7 +197,7 @@ function loadText(text, filename) {
197
197
  syncAll('load');
198
198
  }
199
199
 
200
- // ── Compression helpers (deflate-raw + base64url) ──
200
+ // ── Compression helpers (brotli + base64url) ──
201
201
 
202
202
  function toBase64Url(bytes) {
203
203
  var bin = Array.from(new Uint8Array(bytes), function(b) { return String.fromCharCode(b); }).join('');
@@ -213,27 +213,13 @@ function fromBase64Url(str) {
213
213
  }
214
214
 
215
215
  async function compressText(text) {
216
+ await BrotliWasm.ready;
216
217
  var encoded = new TextEncoder().encode(text);
217
- var cs = new CompressionStream('deflate-raw');
218
- var writer = cs.writable.getWriter();
219
- writer.write(encoded);
220
- writer.close();
221
- var chunks = [];
222
- var reader = cs.readable.getReader();
223
- while (true) {
224
- var result = await reader.read();
225
- if (result.done) break;
226
- chunks.push(result.value);
227
- }
228
- var total = chunks.reduce(function(n, c) { return n + c.length; }, 0);
229
- var buf = new Uint8Array(total);
230
- var offset = 0;
231
- for (var i = 0; i < chunks.length; i++) { buf.set(chunks[i], offset); offset += chunks[i].length; }
232
- return toBase64Url(buf);
218
+ var compressed = BrotliWasm.compress(encoded, { quality: 11 });
219
+ return toBase64Url(compressed);
233
220
  }
234
221
 
235
- async function decompressText(b64url) {
236
- var bytes = fromBase64Url(b64url);
222
+ async function decompressDeflate(bytes) {
237
223
  var ds = new DecompressionStream('deflate-raw');
238
224
  var writer = ds.writable.getWriter();
239
225
  writer.write(bytes);
@@ -252,6 +238,18 @@ async function decompressText(b64url) {
252
238
  return new TextDecoder().decode(buf);
253
239
  }
254
240
 
241
+ async function decompressText(b64url) {
242
+ await BrotliWasm.ready;
243
+ var bytes = fromBase64Url(b64url);
244
+ // Try brotli first, fall back to deflate for old URLs
245
+ try {
246
+ var decompressed = BrotliWasm.decompress(bytes);
247
+ return new TextDecoder().decode(decompressed);
248
+ } catch (_) {
249
+ return decompressDeflate(bytes);
250
+ }
251
+ }
252
+
255
253
  // ── Auto-save to URL hash ──────────────────────────
256
254
 
257
255
  function updateHash() {
@@ -263,7 +261,10 @@ function updateHash() {
263
261
  }
264
262
  var params = new URLSearchParams();
265
263
  if (!S._isDefaultState) {
266
- var meta = Object.assign({}, S.currentMeta, { styles: S.collectStyles() });
264
+ var styles = SDocStyles.stripStyleDefaults(S.collectStyles());
265
+ var meta = Object.assign({}, S.currentMeta);
266
+ if (Object.keys(styles).length > 0) meta.styles = styles;
267
+ else delete meta.styles;
267
268
  var full = SDocYaml.serializeFrontMatter(meta) + '\n' + S.currentBody;
268
269
  var compressed = await compressText(full);
269
270
  params.set('md', compressed);
@@ -415,6 +416,10 @@ document.getElementById('factory-reset-styles').addEventListener('click', functi
415
416
 
416
417
  document.getElementById('toolbar-brand').addEventListener('click', function(e) {
417
418
  e.preventDefault();
419
+ if (window.location.hash && window.location.hash.indexOf('md=') !== -1) {
420
+ window.open(window.location.origin + window.location.pathname, '_blank');
421
+ return;
422
+ }
418
423
  S.resetAllStyles();
419
424
  loadText(DEFAULT_MD);
420
425
  S._isDefaultState = true;
@@ -476,19 +481,26 @@ S.loadText = loadText;
476
481
  // Sync theme tabs to initial theme
477
482
  S.updateThemeTabs(S.activeTheme);
478
483
 
479
- // ── Init ──────────────────────────────────
484
+ // ── Load document from URL hash ──────────────────────────────────
480
485
 
481
- (async function () {
482
- await _defaultReady;
483
- if (window.location.pathname === '/new') {
484
- startNewDocument();
485
- return;
486
- }
486
+ var _lastLoadedHash = null;
487
+
488
+ async function loadFromHash() {
487
489
  var hash = window.location.hash.slice(1);
490
+ if (hash === _lastLoadedHash) return;
491
+ _lastLoadedHash = hash;
492
+
493
+ clearTimeout(S._hashTimer);
494
+
488
495
  var params = hash ? new URLSearchParams(hash) : new URLSearchParams();
489
496
  var mdParam = params.get('md');
490
497
  var modeParam = params.get('mode');
491
498
  var stylesParam = params.get('styles');
499
+ var themeParam = params.get('theme');
500
+ var secParam = params.get('sec');
501
+
502
+ S.resetAllStyles();
503
+
492
504
  if (mdParam) {
493
505
  try {
494
506
  S._isDefaultState = false;
@@ -498,12 +510,29 @@ S.updateThemeTabs(S.activeTheme);
498
510
  console.warn('sdocs-dev: could not decode hash', e);
499
511
  }
500
512
  }
513
+
514
+ if (themeParam === 'light' || themeParam === 'dark') {
515
+ var savedPref = localStorage.getItem('sdocs-theme');
516
+ S.switchThemeAndUpdate(themeParam);
517
+ // Restore localStorage — URL theme is view-only, not persistent
518
+ if (savedPref) localStorage.setItem('sdocs-theme', savedPref);
519
+ else localStorage.removeItem('sdocs-theme');
520
+ } else {
521
+ // Restore user's actual preferred theme (previous hash may have overridden it)
522
+ var preferred = S.getPreferredTheme();
523
+ if (preferred !== S.activeTheme) {
524
+ S.switchThemeAndUpdate(preferred);
525
+ }
526
+ }
527
+
501
528
  if (modeParam && ['read', 'style', 'write', 'raw', 'export'].includes(modeParam)) {
502
529
  setMode(modeParam, true);
503
530
  } else {
504
531
  setMode('read', true);
505
532
  }
533
+
506
534
  if (!mdParam) {
535
+ S._isDefaultState = true;
507
536
  loadText(DEFAULT_MD);
508
537
  if (stylesParam) {
509
538
  try {
@@ -515,13 +544,11 @@ S.updateThemeTabs(S.activeTheme);
515
544
  }
516
545
  }
517
546
 
518
- var secParam = params.get('sec');
519
547
  if (secParam) {
520
548
  setTimeout(function() {
521
549
  var target = document.getElementById(secParam);
522
550
  if (!target) return;
523
551
 
524
- // Expand the target's own section body (its content)
525
552
  var ownSection = target.closest('.md-section');
526
553
  if (ownSection) {
527
554
  var ownBody = ownSection.querySelector(':scope > .md-section-body');
@@ -530,7 +557,6 @@ S.updateThemeTabs(S.activeTheme);
530
557
  if (ownToggle) { ownToggle.classList.add('open'); }
531
558
  }
532
559
 
533
- // Expand all ancestor section bodies
534
560
  var el = target.closest('.md-section-body');
535
561
  while (el) {
536
562
  el.classList.add('open');
@@ -542,7 +568,6 @@ S.updateThemeTabs(S.activeTheme);
542
568
  el = el.parentElement ? el.parentElement.closest('.md-section-body') : null;
543
569
  }
544
570
 
545
- // Add scroll spacer so the target can reach the top
546
571
  var spacerNeeded = contentArea.clientHeight - (contentArea.scrollHeight - target.offsetTop);
547
572
  if (spacerNeeded > 0) {
548
573
  var spacer = document.createElement('div');
@@ -554,8 +579,31 @@ S.updateThemeTabs(S.activeTheme);
554
579
  target.scrollIntoView({ behavior: 'smooth', block: 'start' });
555
580
  }, 200);
556
581
  }
582
+
583
+ if (!secParam) {
584
+ contentArea.scrollTop = 0;
585
+ }
586
+ }
587
+
588
+ // ── Init ──────────────────────────────────
589
+
590
+ (async function () {
591
+ await _defaultReady;
592
+ if (window.location.pathname === '/new') {
593
+ startNewDocument();
594
+ return;
595
+ }
596
+ await loadFromHash();
557
597
  }());
558
598
 
599
+ window.addEventListener('hashchange', function () {
600
+ loadFromHash();
601
+ });
602
+
603
+ window.addEventListener('popstate', function () {
604
+ loadFromHash();
605
+ });
606
+
559
607
  // ── Toolbar scroll hints (fade + bounce-peek) ──────
560
608
 
561
609
  function initScrollHint(el) {