@atom-forge/brickee 0.0.0-stage → 0.0.2

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.
Files changed (85) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README-AI.md +286 -0
  4. package/README.md +41 -2
  5. package/dist/Editor.svelte +1956 -0
  6. package/dist/Editor.svelte.d.ts +19 -0
  7. package/dist/LinkPrompt.svelte +42 -0
  8. package/dist/LinkPrompt.svelte.d.ts +8 -0
  9. package/dist/PluginHost.svelte +41 -0
  10. package/dist/PluginHost.svelte.d.ts +13 -0
  11. package/dist/Preview.svelte +182 -0
  12. package/dist/Preview.svelte.d.ts +32 -0
  13. package/dist/PropsBar.svelte +405 -0
  14. package/dist/PropsBar.svelte.d.ts +17 -0
  15. package/dist/code.d.ts +12 -0
  16. package/dist/code.js +15 -0
  17. package/dist/dom.d.ts +29 -0
  18. package/dist/dom.js +224 -0
  19. package/dist/i18n.d.ts +7 -0
  20. package/dist/i18n.js +18 -0
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.js +16 -0
  23. package/dist/kit/Empty.svelte +24 -0
  24. package/dist/kit/Empty.svelte.d.ts +8 -0
  25. package/dist/kit/Frame.svelte +76 -0
  26. package/dist/kit/Frame.svelte.d.ts +15 -0
  27. package/dist/kit/util.d.ts +13 -0
  28. package/dist/kit/util.js +28 -0
  29. package/dist/locales/hu.d.ts +2 -0
  30. package/dist/locales/hu.js +142 -0
  31. package/dist/markdown.d.ts +23 -0
  32. package/dist/markdown.js +219 -0
  33. package/dist/model.d.ts +21 -0
  34. package/dist/model.js +45 -0
  35. package/dist/palette.d.ts +19 -0
  36. package/dist/palette.js +23 -0
  37. package/dist/plugins/Gap.svelte +28 -0
  38. package/dist/plugins/Gap.svelte.d.ts +15 -0
  39. package/dist/plugins/GapPreview.svelte +17 -0
  40. package/dist/plugins/GapPreview.svelte.d.ts +5 -0
  41. package/dist/plugins/Map.svelte +47 -0
  42. package/dist/plugins/Map.svelte.d.ts +12 -0
  43. package/dist/plugins/MapPreview.svelte +26 -0
  44. package/dist/plugins/MapPreview.svelte.d.ts +5 -0
  45. package/dist/plugins/Table.svelte +537 -0
  46. package/dist/plugins/Table.svelte.d.ts +28 -0
  47. package/dist/plugins/TablePreview.svelte +62 -0
  48. package/dist/plugins/TablePreview.svelte.d.ts +5 -0
  49. package/dist/plugins/YouTube.svelte +52 -0
  50. package/dist/plugins/YouTube.svelte.d.ts +8 -0
  51. package/dist/plugins/YouTubePreview.svelte +76 -0
  52. package/dist/plugins/YouTubePreview.svelte.d.ts +5 -0
  53. package/dist/plugins/index.d.ts +20 -0
  54. package/dist/plugins/index.js +105 -0
  55. package/dist/plugins/util.d.ts +3 -0
  56. package/dist/plugins/util.js +8 -0
  57. package/dist/schema.d.ts +11 -0
  58. package/dist/schema.js +25 -0
  59. package/dist/scroll.d.ts +18 -0
  60. package/dist/scroll.js +57 -0
  61. package/dist/types.d.ts +132 -0
  62. package/dist/types.js +43 -0
  63. package/dist/view.d.ts +6 -0
  64. package/dist/view.js +11 -0
  65. package/docs/en/bundled-plugins.md +99 -0
  66. package/docs/en/documents-and-markdown.md +114 -0
  67. package/docs/en/editor-and-preview.md +139 -0
  68. package/docs/en/getting-started.md +85 -0
  69. package/docs/en/host-contexts.md +169 -0
  70. package/docs/en/index.md +38 -0
  71. package/docs/en/recipes-and-pitfalls.md +123 -0
  72. package/docs/en/server-side.md +95 -0
  73. package/docs/en/upgrading.md +71 -0
  74. package/docs/en/writing-plugins.md +405 -0
  75. package/docs/hu/bundled-plugins.md +99 -0
  76. package/docs/hu/documents-and-markdown.md +114 -0
  77. package/docs/hu/editor-and-preview.md +139 -0
  78. package/docs/hu/getting-started.md +85 -0
  79. package/docs/hu/host-contexts.md +169 -0
  80. package/docs/hu/index.md +38 -0
  81. package/docs/hu/recipes-and-pitfalls.md +123 -0
  82. package/docs/hu/server-side.md +95 -0
  83. package/docs/hu/upgrading.md +71 -0
  84. package/docs/hu/writing-plugins.md +405 -0
  85. package/package.json +114 -5
package/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.0.2] - 2026-11-10
11
+
12
+ ### Added
13
+
14
+ - Showcase site in `src/routes` (`bun run dev`), styled like the `@atom-forge/ui` site: a playground (editor beside its preview, stored JSON and markdown), the guide (editor and preview, split view, markdown, stored form and schema), a page per bundled plugin, writing, nesting and overriding plugins, image blocks, and the host contexts (per-view values, code languages, localisation), each with live examples and their code. It resolves `@atom-forge/brickee` to the local source and prerenders to `build/` (`@sveltejs/adapter-static`); it is not part of the npm package.
15
+ - A `toggles` field kind (`PropField`): a row of icon buttons, each switching one of `options` on or off (`options[].icon` is the button, `label` its tooltip), stored as the list of the ones on.
16
+ - `provideCodeLanguages(() => list)` (with `useCodeLanguages` and the `CodeLanguage` type): a host context that turns a code block's language field into a dropdown of the given languages, with *None* first; without it the language stays free text. Nested editors inherit the list, and a stored language missing from it stays an option.
17
+
18
+ ### Changed
19
+
20
+ - The fields bar is more compact: smaller controls and type, text fields show their label as the placeholder (with the field's own placeholder after it), the other fields a short label beside the control.
21
+ - The table's header and footer rows are two icon toggles in its fields bar (a `toggles` field), like any plugin field; the toolbar above the table holds only the row and column tools of the cell being edited, and says to click a cell while none is. The info icon with the keyboard hint is gone.
22
+ - The editor follows the host's light and dark mode: its colours (block fields, popups, frames, the table editor, the block actions) come from the `@atom-forge/ui` theme tokens with the former colours as fallbacks, and the props bar and the link prompt no longer force the light theme. The previews' table borders, video captions and the gap's stripes are drawn from the text colour.
23
+ - A code block's language is edited in the same fields bar as a plugin's (its header shows `code · <language>`), instead of a small input in its corner; read-only, the corner still shows the language.
24
+ - The block handles keep 1px free above and below, so the handles of neighbouring blocks do not touch.
25
+ - The docs note that the block handles are drawn about 22 px left of the editor's box, so the element around it needs that much left padding.
26
+ - Releases are configured with `ship-it init`: the publish workflow comes from Ship's template, `package.json` gains `pub-validate` and `publishConfig`, and npm Trusted Publishing lets CI publish the next versions.
27
+
28
+ ### Removed
29
+
30
+ - **Breaking:** images and media are no longer the library's concept — whether a block stores a URL or an id, how the user picks an image and how it is resolved is the application's decision. Removed: the `image` and `images` field kinds with their picker, the `media` prop of `Editor` and `Preview`, `provideMedia`, `useMedia`, `findMedia`, the `MediaItem` type, `toMarkdown`'s `media` option and `MarkdownContext.media`. An image block is an app plugin that picks in its own view or dialog and resolves with the app's own data; stored documents are unaffected. See the upgrading chapter.
31
+
32
+ ## [0.0.1] - 2026-11-10
33
+
34
+ ### Added
35
+
36
+ - The block editor, moved here from the gamer365 project (where it was the `block-editor` workspace package), as `@atom-forge/brickee`: the markdown core with the editor, the preview, the JSON stored form and the markdown conversions; the kit for writing plugins (`Frame`, `Empty`, block fields, media / view / text contexts, helpers); the bundled plugins in `@atom-forge/brickee/plugins` (general blocks only: YouTube, map, table, gap — the app-specific ones, such as boxes, callouts, images and the supporters-only divider, stay in the app that gave them their meaning); the zod schema in `@atom-forge/brickee/schema`; the Hungarian UI text in `@atom-forge/brickee/locales/hu`.
37
+ - `BlockPlugin.preview` is optional: an app with its own renderer leaves it out, and `<Preview>` skips such blocks. The bundled plugins keep a default preview, which an app may use or replace.
38
+ - An optional markdown form for plugins (`BlockPlugin.markdown`), used by `toMarkdown(blocks, { plugins, media })` and by copying in the editor; the bundled plugins have one where it makes sense (YouTube and map as a link, table as a GFM table).
39
+ - Scroll sync: `scrollSync()` keeps a preview box scrolled to the same fraction as the editor's box (one way, also when the preview's content changes size; `use:sync.source` / `use:sync.target`, `align()`), with `scrollFraction` / `setScrollFraction` for keeping a position across a layout change. Proportional only: the two show roughly the same place, not the same block.
40
+ - Repository frame following the AtomForge packages: agent instructions, book, English and Hungarian docs, `README-AI.md`, `TODO.md`, `notes/`, and a tag-triggered publish workflow with `@atom-forge/ship` 0.2.0 (installed from npm).
41
+
42
+ ---
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gergely Laborci
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README-AI.md ADDED
@@ -0,0 +1,286 @@
1
+ # @atom-forge/brickee — AI guide
2
+
3
+ Compact reference for coding agents using `@atom-forge/brickee` in a Svelte 5 project. It is derived from [`docs/en/`](docs/en/index.md); where the two disagree, `docs/en/` is authoritative.
4
+
5
+ Brickee is a block editor. Its core is a markdown block editor (`md:p`, `md:h1`…`md:h6`, `md:ul`, `md:ol`, `md:todo`, `md:quote`, `md:code`, `md:hr`) plus the kit plugins are written with; every other block is a plugin with an editor view, a site preview, fields and optionally a markdown form. Documents are stored as JSON (`StoredBlock[]`). The bundled plugins (`@atom-forge/brickee/plugins`) are plugins like any other, opt-in.
6
+
7
+ ## Rules
8
+
9
+ - Peers: `svelte` 5, `@atom-forge/ui` (popups and field controls), `@lucide/svelte`; `zod` only for `@atom-forge/brickee/schema`. The app must set up `@atom-forge/ui` (Tailwind 4, its theme CSS and `@source`, the app beneath its `Root`).
10
+ - Store the editor's `value` (`StoredBlock[]`) as it is. Built-in types start with `md:`; any other `type` is a plugin's. Never rename a plugin's `type`: stored documents refer to it.
11
+ - Pass the same `plugins` to the `Editor` and the `Preview` of a document type. Restrict what an editor offers with `palette` (palette ids: `md:*` kinds and plugin types), not by dropping plugins.
12
+ - The editor's colours come from the `@atom-forge/ui` theme tokens (light/dark follow the host). Its block handles sit ~22 px left of its box: leave that much left padding around it.
13
+ - No app-specific editor props: app data reaches plugins through contexts (`provideView`, `provideCodeLanguages`, `messages` / `provideText`), plugin fields, or the app's own modules and contexts.
14
+ - No image concept: no image field, picker or media list. An image block is an app plugin that stores what the app chooses (a URL, or an id of its own media library), picks it in its view or the app's own dialog, and resolves ids with the app's own data.
15
+ - Plugin props are untrusted user input. Previews sanitize: `safeColor` for colours, check links and image addresses (`http(s)` or site-relative), clamp numbers, never `{@html}` with props.
16
+ - A plugin view declares both `props` and `blocks` with `$bindable()`, even if unused.
17
+ - Containers (`content: 'blocks'`) nest one level only; nested editors never offer containers or unique plugins. `accepts` narrows their content (`'md:*'` = every built-in).
18
+ - `unique`: one per document, top level; not enforced by the schema. `gate`: the preview only *marks* blocks after it; hide gated content on the server.
19
+ - `toMarkdown` and `toStored` use the DOM: browser only. `fromMarkdown`, `fromStored`, `inline`, the schema and `./model` run anywhere.
20
+ - Validate saved documents on the server with `blocksSchemaFor(plugins)`; it checks shape and `accepts`, not known types, uniqueness or prop contents.
21
+ - Scroll sync is proportional (same scroll fraction), one way, editor → preview; it needs no markup and shows roughly the same place, not the same block.
22
+ - `<Preview>` is optional: the editor doesn't depend on it. An app with its own renderer can render `StoredBlock[]` itself and omit plugin `preview`s; bundled plugins ship a default preview you may use, replace (`{ ...youtube, preview: Mine }`) or ignore.
23
+ - UI text: English source text is the key; pass `messages` (e.g. `hu` from `@atom-forge/brickee/locales/hu`, extended with your own keys) to the top editor/preview.
24
+
25
+ ## Public API
26
+
27
+ ```ts
28
+ // @atom-forge/brickee — components
29
+ // <Editor bind:value={StoredBlock[]} plugins?={BlockPlugin[]} palette?={string[]} messages?={Messages}
30
+ // readonly?={boolean} placeholder?={string} /> // `nested` is for Frame only
31
+ // <Preview blocks={StoredBlock[]} plugins={BlockPlugin[]} messages?={Messages}
32
+ // markGated?={boolean /* default true */} nested?={boolean} />
33
+ // <Frame bind:blocks {readonly} {palette} {plugins} heading?={string} empty?={string} image?={string} placeholder?={string} />
34
+ // <Empty icon={Icon} text={string} />
35
+
36
+ interface StoredBlock { type: string; indent: number; text?: string; props?: Record<string, unknown>; blocks?: StoredBlock[] }
37
+ const MD = 'md:'; const MAX_INDENT = 6; const MAX_DEPTH = 2;
38
+ function isMd(b: StoredBlock): boolean;
39
+ function acceptsId(accepts: readonly string[] | undefined, id: string): boolean;
40
+ function inlineToPlainText(md: string): string;
41
+ function blocksToPlainText(blocks: StoredBlock[]): string;
42
+
43
+ // markdown (Block = the editor's working form: { id, type, html, indent, checked?, props?, plugin?, blocks? })
44
+ function inline(md: string): string; // inline markdown -> safe inline HTML
45
+ function fromStored(s: StoredBlock): Block;
46
+ function toStored(b: Block): StoredBlock; // browser only
47
+ function fromMarkdown(src: string): Block[]; // one block per non-empty line; built-in blocks only
48
+ function toMarkdown(blocks: Block[], opts?: { plugins?: BlockPlugin[] }): string; // browser only
49
+
50
+ // contexts (call provide* in component init, above the editor/preview; use* in plugin components)
51
+ function provideView(get: () => string | undefined): void;
52
+ function useView(): () => string | undefined;
53
+ interface CodeLanguage { value: string; label?: string } // value: stored as the code block's props.lang
54
+ function provideCodeLanguages(own: () => (string | CodeLanguage)[] | undefined): void; // list: the language field is a dropdown
55
+ function useCodeLanguages(): () => CodeLanguage[]; // empty: free text
56
+ type Messages = Record<string, string>;
57
+ type Translate = (text: string, vars?: Record<string, string | number>) => string; // fills {name}
58
+ function provideText(own: () => Messages | undefined): void;
59
+ function useText(): Translate;
60
+ function translator(messages?: Messages): Translate;
61
+
62
+ // scroll sync (editor -> preview, by scroll fraction)
63
+ interface ScrollSync {
64
+ source: Action<HTMLElement>; // use:sync.source on the editor's scrolling box
65
+ target: Action<HTMLElement, boolean | undefined>; // use:sync.target={on} on the preview's (default on)
66
+ align(): void; // re-sync now (e.g. after the preview width changed)
67
+ }
68
+ function scrollSync(): ScrollSync;
69
+ function scrollFraction(box: HTMLElement): number; // 0…1 (0 if it does not scroll)
70
+ function setScrollFraction(box: HTMLElement, f: number): void;
71
+
72
+ // palette
73
+ interface PaletteItem { id: string; label: string; hint: string; keys: string; icon: Icon }
74
+ const PALETTE_ITEMS: PaletteItem[]; const BUILT_IN_IDS: string[];
75
+ function narrowPalette(palette: string[], accepts: readonly string[] | undefined): string[];
76
+
77
+ // kit helpers
78
+ function safeColor(c: unknown, fallback: string): string; // #hex (3–8 digits) or fallback
79
+ function lines(s: string): string[]; // trimmed non-empty lines
80
+ function baseValue(v: unknown): unknown; // base of a per-view prop
81
+ function viewValues(v: unknown, keys: string[]): unknown[]; // per-view prop, inheritance applied
82
+ function mdText(s: unknown): string; // plain text safe in markdown
83
+ function mdJoin(...parts: (string | false | null | undefined)[]): string; // blank-line join, empties dropped
84
+ function mdQuote(md: string): string; // '> ' every line
85
+
86
+ // plugins
87
+ interface BlockPlugin<P extends Props = Props> {
88
+ type: string; // not 'md:'; schema: /^[a-z][a-z0-9:-]{0,39}$/
89
+ label: string; hint: string; keys: string; icon: Icon;
90
+ props: () => P; // a new block's props
91
+ fields?: PropField[];
92
+ tag(props: P): string; // mini label; ' · ' parts translated separately
93
+ content: 'none' | 'text' | 'plain' | 'blocks'; // plugins use 'none' or 'blocks'
94
+ view: Component<PluginViewProps<any>, {}, 'props' | 'blocks'>;
95
+ preview?: Component<PluginPreviewProps<any>>; // optional: without it <Preview> renders nothing for the block
96
+ accepts?: string[]; // container content as palette ids; 'md:*'
97
+ markdown?(props: P, ctx: MarkdownContext): string; // '' = left out
98
+ unique?: boolean;
99
+ gate?: boolean;
100
+ }
101
+ interface PluginViewProps<P> { props: P; blocks: StoredBlock[]; readonly: boolean; palette: string[]; plugins: BlockPlugin[] }
102
+ interface PluginPreviewProps<P> { props: P; blocks: StoredBlock[]; plugins: BlockPlugin[] }
103
+ interface MarkdownContext { inner: string }
104
+ interface PropField {
105
+ key: string; label: string;
106
+ kind: 'text' | 'textarea' | 'color' | 'select' | 'slider' | 'toggles'; // toggles: icon buttons, stores the list of the ones on
107
+ placeholder?: string;
108
+ options?: { value: string; label: string; icon?: Icon }[]; // select; stepped slider (label may use {n}); toggles (icon + tooltip)
109
+ min?: number; max?: number; step?: number; unit?: string; // number slider (stored as string)
110
+ none?: string; // unset label (number slider notch, optional colour)
111
+ views?: { key: string; label: string }[]; // per-view slider values
112
+ format?: (value: unknown) => string;
113
+ parse?: (text: string) => unknown; // '' / null removes the key
114
+ }
115
+
116
+ // @atom-forge/brickee/plugins
117
+ // table, youtube, map, gap — the only bundled plugins (none holds nested blocks or images); PLUGINS = [table, youtube, map, gap]
118
+ // types: TableProps, YouTubeProps, MapProps, GapProps, Head
119
+ // helpers: youtubeId, mapSrc, gapLines, GAP_LINES, GAP_MAX, tableHeads, rowHead, HEADS, square
120
+ // Image blocks, boxes and other containers are app plugins you write yourself (see docs/en/writing-plugins.md).
121
+
122
+ // @atom-forge/brickee/schema (zod)
123
+ const blocksSchema: ZodType<StoredBlock[]>;
124
+ function blocksSchemaFor(plugins: readonly { type: string; accepts?: readonly string[] }[]): ZodType<StoredBlock[]>;
125
+
126
+ // @atom-forge/brickee/model — StoredBlock, MD, MAX_INDENT, MAX_DEPTH, isMd, acceptsId, inlineToPlainText, blocksToPlainText
127
+ // @atom-forge/brickee/locales/hu — hu: Messages
128
+ ```
129
+
130
+ ## Minimal example
131
+
132
+ ```svelte
133
+ <script lang="ts">
134
+ import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
135
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
136
+ import { hu } from '@atom-forge/brickee/locales/hu';
137
+
138
+ let value = $state<StoredBlock[]>([]);
139
+ </script>
140
+
141
+ <Editor bind:value plugins={PLUGINS} messages={hu} />
142
+ <Preview blocks={value} plugins={PLUGINS} messages={hu} />
143
+ ```
144
+
145
+ ## Common extensions
146
+
147
+ **Writing a plugin** — a props interface, a view, a preview, the object:
148
+
149
+ ```svelte
150
+ <!-- ButtonView.svelte -->
151
+ <script lang="ts" module>
152
+ export interface ButtonProps extends Record<string, unknown> { label: string; url: string; color: string }
153
+ </script>
154
+ <script lang="ts">
155
+ import { MousePointerClick } from '@lucide/svelte';
156
+ import { Empty, type PluginViewProps } from '@atom-forge/brickee';
157
+ let { props = $bindable(), blocks = $bindable() }: PluginViewProps<ButtonProps> = $props();
158
+ </script>
159
+ {#if props.label}<span>{props.label}</span>{:else}<Empty icon={MousePointerClick} text="Set the label and the link above" />{/if}
160
+ ```
161
+
162
+ ```svelte
163
+ <!-- ButtonPreview.svelte -->
164
+ <script lang="ts">
165
+ import { safeColor, type PluginPreviewProps } from '@atom-forge/brickee';
166
+ import type { ButtonProps } from './ButtonView.svelte';
167
+ let { props }: PluginPreviewProps<ButtonProps> = $props();
168
+ const href = $derived(/^(https?:|mailto:|\/(?!\/))/.test(String(props.url ?? '')) ? String(props.url) : '');
169
+ </script>
170
+ {#if href && props.label}<a {href} style:background={safeColor(props.color, '#2563eb')}>{props.label}</a>{/if}
171
+ ```
172
+
173
+ ```ts
174
+ // button.ts
175
+ import { MousePointerClick } from '@lucide/svelte';
176
+ import { mdText, type BlockPlugin } from '@atom-forge/brickee';
177
+ import ButtonView, { type ButtonProps } from './ButtonView.svelte';
178
+ import ButtonPreview from './ButtonPreview.svelte';
179
+
180
+ export const button: BlockPlugin<ButtonProps> = {
181
+ type: 'button', label: 'Button', hint: 'A link as a button', keys: 'button link cta', icon: MousePointerClick,
182
+ props: () => ({ label: '', url: '', color: '#2563eb' }),
183
+ fields: [
184
+ { key: 'label', label: 'Label', kind: 'text' },
185
+ { key: 'url', label: 'Link', kind: 'text', placeholder: 'https://…' },
186
+ { key: 'color', label: 'Colour', kind: 'color' }
187
+ ],
188
+ tag: () => 'button',
189
+ content: 'none',
190
+ markdown: (p) => (p.url ? `[${mdText(p.label)}](${p.url})` : ''),
191
+ view: ButtonView,
192
+ preview: ButtonPreview
193
+ };
194
+ ```
195
+
196
+ A **container** uses `content: 'blocks'`, `accepts`, `<Frame bind:blocks {readonly} {palette} {plugins} />` in its view and `<Preview {blocks} {plugins} nested />` in its preview; its `markdown` gets the inner blocks as `ctx.inner` (`mdQuote(mdJoin(title, inner))`).
197
+
198
+ **Overriding a preview / adding fields** — spread the plugin, keep `type`:
199
+
200
+ ```ts
201
+ import type { BlockPlugin } from '@atom-forge/brickee';
202
+ import { map, youtube } from '@atom-forge/brickee/plugins';
203
+ import SiteYouTube from './SiteYouTube.svelte'; // receives PluginPreviewProps<YouTubeProps>
204
+ import SiteMap from './SiteMap.svelte';
205
+
206
+ export const plugins: BlockPlugin[] = [
207
+ { ...youtube, preview: SiteYouTube },
208
+ {
209
+ ...map,
210
+ props: () => ({ ...map.props(), caption: '' }),
211
+ fields: [...(map.fields ?? []), { key: 'caption', label: 'Caption', kind: 'text' }],
212
+ preview: SiteMap
213
+ }
214
+ ];
215
+ ```
216
+
217
+ **Markdown form and conversion** — `markdown(props, { inner })` returns markdown or `''`; use `mdText`, `mdJoin`, `mdQuote`. Whole document: `toMarkdown(doc.map(fromStored), { plugins })`; import: `fromMarkdown(text).map(toStored)` (both in the browser).
218
+
219
+ **Per-view fields** — add `views` to a slider field and provide the current view:
220
+
221
+ ```ts
222
+ import type { BlockPlugin } from '@atom-forge/brickee';
223
+
224
+ const VIEWS = [{ key: 'desktop', label: 'Desktop' }, { key: 'phone', label: 'Phone' }];
225
+ const perView = (p: BlockPlugin, key: string): BlockPlugin => ({ ...p, fields: p.fields?.map((f) => (f.key === key ? { ...f, views: VIEWS } : f)) });
226
+ // host component around <Editor>: provideView(() => currentViewKey);
227
+ // preview: const [desktop, phone] = viewValues(props.ratio, ['desktop', 'phone']);
228
+ ```
229
+
230
+ Stored as the base value alone (`"56"`) or an object by view key (`{ "desktop": "56", "phone": "100" }`); a view without its own value inherits from the view before it.
231
+
232
+ **Scroll sync (split view)** — one way, editor → preview, same scroll fraction; re-syncs on source scroll and target resize:
233
+
234
+ ```svelte
235
+ <script lang="ts">
236
+ import { Editor, Preview, scrollSync, type StoredBlock } from '@atom-forge/brickee';
237
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
238
+ let value = $state<StoredBlock[]>([]);
239
+ const sync = scrollSync();
240
+ </script>
241
+ <div style="overflow-y: auto; height: 100vh" use:sync.source><Editor bind:value plugins={PLUGINS} /></div>
242
+ <div style="overflow-y: auto; height: 100vh" use:sync.target><Preview blocks={value} plugins={PLUGINS} /></div>
243
+ ```
244
+
245
+ Keep the place across a layout change: `const f = scrollFraction(box); change(); await tick(); setScrollFraction(box, f);`.
246
+
247
+ **Server validation**:
248
+
249
+ ```ts
250
+ import { blocksSchemaFor } from '@atom-forge/brickee/schema';
251
+ const result = blocksSchemaFor(plugins).safeParse(input); // or pass [{ type, accepts }] only
252
+ ```
253
+
254
+ ## Pitfalls
255
+
256
+ | Symptom | Cause | Fix |
257
+ |---|---|---|
258
+ | Fields and popups unstyled | `@atom-forge/ui` theme CSS / `@source` missing | Set up `@atom-forge/ui`; render beneath its `Root`. |
259
+ | Block handles stick out on the left | Handles are drawn ~22 px left of the editor's box | Give the editor's container ~22 px left padding. |
260
+ | Context errors with a linked local copy | Duplicate `svelte` / `@atom-forge/ui` | Vite `resolve.dedupe: ['svelte', '@atom-forge/ui', '@lucide/svelte', 'zod']`. |
261
+ | A block is missing from `<Preview>` | Its plugin has no `preview` | Add one (bundled plugins have a default) or render it yourself. |
262
+ | "Unknown block" in the editor, block missing on the site | Plugin missing from `plugins` | Same plugins for editor and preview. |
263
+ | Image block renders nothing | Preview rejects the stored address, or the app's lookup lacks the id | Store `http(s)` / site-relative addresses; resolve ids from the same app data everywhere. |
264
+ | `media` prop, `useMedia`, `findMedia`, `kind: 'image'` missing | Removed: images are the app's business | Own picking and lookup in the app's image plugin (see `docs/en/upgrading.md`). |
265
+ | Box not offered inside a box | One nesting level | By design. |
266
+ | Block not offered inside a container | `accepts` or host `palette` excludes it | Add its palette id (or `'md:*'`). |
267
+ | Gated content visible to all | `gate` only marks | Cut the document on the server. |
268
+ | `document is not defined` | `toMarkdown` / `toStored` during SSR | Call in the browser. |
269
+ | Injected colour / link | Props rendered unchecked | `safeColor`, link checks, no `{@html}`. |
270
+ | Override loses its blocks | `type` changed | Keep `type`. |
271
+ | Bind error in a custom view | `props` / `blocks` not `$bindable()` | Declare both bindable. |
272
+ | Value is never `[]` after mount | An empty document is one empty paragraph | Test emptiness with `blocksToPlainText(doc).trim() === ''`. |
273
+ | Preview does not follow the editor | Actions not on the scrolling boxes | Put them on the `overflow` boxes. |
274
+ | `image`, `callout`, `locked`… not exported from `/plugins` | Only `table`, `youtube`, `map`, `gap` are bundled | Keep app-owned copies with the same `type` (see `docs/en/upgrading.md`). |
275
+ | `safeColor` import fails from `/plugins` | Kit helpers live in the core | Import from `@atom-forge/brickee`. |
276
+
277
+ ## Documentation map
278
+
279
+ In the installed package (`node_modules/@atom-forge/brickee/`):
280
+
281
+ - [`docs/en/index.md`](docs/en/index.md) — full English documentation, one page per chapter: [getting started](docs/en/getting-started.md), [editor and preview](docs/en/editor-and-preview.md), [documents and markdown](docs/en/documents-and-markdown.md), [bundled plugins](docs/en/bundled-plugins.md), [writing plugins](docs/en/writing-plugins.md), [host contexts](docs/en/host-contexts.md), [on the server](docs/en/server-side.md), [recipes and pitfalls](docs/en/recipes-and-pitfalls.md), [upgrading](docs/en/upgrading.md). Authoritative.
282
+ - [`docs/hu/index.md`](docs/hu/index.md) — Hungarian translation.
283
+ - [`CHANGELOG.md`](CHANGELOG.md) — release history.
284
+ - `dist/index.d.ts`, `dist/plugins/index.d.ts` — the exported types of the installed version; `dist/plugins/` — the bundled plugins' source, useful as plugin examples.
285
+
286
+ The book at https://books.atom-forge.eu is the source of these docs and adds the AI Notes chapter: instructions to copy into your project's agent instruction file. To develop the package itself, follow `AGENTS.md` in the repository.
package/README.md CHANGED
@@ -1,3 +1,42 @@
1
- # Temporary Holding Version
1
+ # Brickee
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `@atom-forge/brickee` is a block editor for Svelte 5. Its core is a markdown block editor — paragraphs, headings, lists, todos, quotes, code, rules — and everything else is a plugin: a block with an editor view, a site preview, its fields, and optionally a markdown form. Documents are stored as JSON.
4
+
5
+ - **Core** — the markdown blocks and the kit plugins are written with. Without plugins it is a plain markdown block editor.
6
+ - **Bundled plugins** (`@atom-forge/brickee/plugins`) — a few general blocks: YouTube, map, table, gap. Plugins like any other: pick the ones you need.
7
+ - **Your plugins** — the same `BlockPlugin` shape, in your app.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ bun add @atom-forge/brickee @atom-forge/ui @lucide/svelte
13
+ ```
14
+
15
+ `svelte` 5, `@atom-forge/ui` (popups and form controls; your app needs its theme CSS) and `@lucide/svelte` are peer dependencies; `zod` is an optional one for the server-side schema.
16
+
17
+ ## Example
18
+
19
+ ```svelte
20
+ <script lang="ts">
21
+ import { Editor, Preview, type StoredBlock } from '@atom-forge/brickee';
22
+ import { PLUGINS } from '@atom-forge/brickee/plugins';
23
+ import { hu } from '@atom-forge/brickee/locales/hu';
24
+
25
+ let value = $state<StoredBlock[]>([]);
26
+ </script>
27
+
28
+ <Editor bind:value plugins={PLUGINS} messages={hu} />
29
+ <Preview blocks={value} plugins={PLUGINS} />
30
+ ```
31
+
32
+ ## Documentation
33
+
34
+ - [The Brickee book](https://books.atom-forge.eu) — concepts, plugins, recipes, and pitfalls.
35
+ - [English docs](docs/en/index.md) · [Magyar dokumentáció](docs/hu/index.md)
36
+ - [README-AI.md](README-AI.md) — compact reference for coding agents.
37
+
38
+ Developing Brickee itself starts in [AGENTS.md](AGENTS.md).
39
+
40
+ ## License
41
+
42
+ MIT