# Open Slide Best Practices

> Best practices for authoring presentations with open-slide, the React slide framework with a fixed 1920×1080 canvas, with full Hebrew and RTL support. Covers the slides/[id]/index.tsx file contract, type scale, DesignSystem tokens, themes/ system, @slide-comment inspector markers, current.json deictic resolution, Hebrew Google Fonts (Heebo, Rubik, Assistant, Noto Sans Hebrew), CSS logical properties, bidirectional Hebrew+English text with the bdi element, and Hebrew-aware type scale tuning. Use when authoring or editing slides under slides/[id]/ in an open-slide project, or when building Hebrew or bilingual decks on the framework. Do NOT use for video creation (use remotion-best-practices or hyperframes-best-practices), or for generic Hebrew presentations outside open-slide (use presentation-generator).

- Skill: `skills-il/open-slide-best-practices` (Agent Skill, multi-file: 12 files)
- Install (CLI): `npx skillmds@latest add skills-il/open-slide-best-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/skills-il/open-slide-best-practices/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- License: MIT
- Author: skills-il (https://skillmd.com/u/skills-il)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/skills-il/open-slide-best-practices

---


# open-slide Best Practices for Israeli Developers

> Adapted from [1weiho/open-slide](https://github.com/1weiho/open-slide) (MIT). Hebrew and RTL adaptations by [skills-il](https://agentskills.co.il).

## Problem

open-slide ships an opinionated authoring system: a fixed 1920×1080 React canvas, a typed `DesignSystem` const, a themes folder, an inspector that drops `@slide-comment` markers in source, and five built-in skills (`slide-authoring`, `create-slide`, `create-theme`, `apply-comments`, `current-slide`). None of those touch Hebrew or RTL. Out of the box, a deck written by a coding agent comes out left-aligned, system-font only, with `paddingLeft` that fights the Hebrew reading order, and no guidance for mixed Hebrew+English text. Israeli developers waste hours retrofitting RTL, loading Hebrew Google Fonts, and discovering that the type scale (140-200px hero) which works in Inter looks cramped in Heebo at the same weight.

## Instructions

### When to use

Use this skill whenever you are writing or editing code under `slides/<id>/` in an open-slide project, creating a theme under `themes/`, or processing inspector comments, especially when any of that content is in Hebrew or bilingual.

### New project setup

When in an empty folder or workspace with no existing open-slide project, scaffold one using:

```bash
npx @open-slide/cli init my-slide
cd my-slide
npm run dev
```

Replace `my-slide` with a suitable project name. The scaffolder installs dependencies with npm and prints `npm run dev` as the next step; `pnpm dev` works too if you re-install with pnpm.

**Node version.** `@open-slide/cli` depends on `commander@15`, which declares `"node": ">=22.12.0"`, and on `chalk@6` (`">=22"`). On Node 20 the install still completes but npm prints `EBADENGINE Unsupported engine` for both. Use Node 22.12 or newer.

What the scaffold gives you:

| Path | What it is |
| --- | --- |
| `slides/<id>/index.tsx` | One folder per slide. See the id rule below. |
| `themes/` | Theme markdown files |
| `open-slide.config.ts` | Typed `OpenSlideConfig` export, empty by default. Framework-level options live here: `base`, `slidesDir`, `themesDir`, `assetsDir`, `port`, `allowedHosts`, `build`, and a deprecated `locale`. |
| `.agents/skills/` and `.claude/skills/` | The five upstream skills, copied in at init time |
| `npm run sync:skills` | Re-copies the bundled skills out of `@open-slide/core` after a core upgrade |

Run `npm run sync:skills` after upgrading `@open-slide/core`, otherwise the agent keeps reading the skill copies from the version you scaffolded with.

### Which version you are on

Target **`@open-slide/core` 1.19.x**. That is what `npx @open-slide/cli init` installs today, and everything in this skill is verified against 1.19.1.

A **2.0.0 beta** is published (React 19, Vite 8, TypeScript 7). It is a pre-release: npm's `latest` tag still resolves to 1.19.x and the beta sits behind its own `beta` dist-tag, so you only reach v2 by asking for it (`@open-slide/cli@2.0.0-beta.1` or `@beta`). Do not scaffold production work onto it, and do not assume any v2 API detail is stable.

Your **slide source is not the problem**. The upstream migration guide is explicit that v1 slides need no changes and that the migration is confined to `package.json`, your Node version, and your deploy config. Everything this skill teaches about the file contract, the canvas, type scale, themes and Hebrew/RTL carries over unchanged.

**The one thing that breaks on upgrade.** A v1 scaffold puts `vite` in your `devDependencies`; core 2.x ships its own Vite 8 internally. Leave the old entry in place and the dev server and build both refuse to start:

```
✖ @tailwindcss/vite resolves vite@5.4.21 (node_modules/vite), but @open-slide/core ships
vite@8.2.2 (node_modules/@open-slide/core/node_modules/vite).
A `vite` entry in your package.json (v1 workspaces had one) shadows the copy core depends on.
Remove it and reinstall.
```

Delete the `vite` devDependency and reinstall. Fresh v2 scaffolds omit it and are unaffected. Migration guide: <https://open-slide.dev/docs/migrate-to-v2>.

### Slide folder ids must be ASCII (Hebrew folder names are silently dropped)

**Do not name a slide folder in Hebrew.** Discovery filters every folder under `slides/` through `SLIDE_ID_RE = /^[a-z0-9_-]+$/i` and skips the rest, so a folder like `slides/פתיחה/` never reaches the sidebar, the browser, or the build. Re-verified on `@open-slide/core@1.19.1` (the current stable release) by scaffolding a project and running `npm run build`:

```
[plugin:open-slide] [plugin open-slide] Ignoring slide folder "פתיחה": slide ids must match
/^[a-z0-9_-]+$/i (lowercase/uppercase letters, digits, "-", "_"). Rename the folder under
"slides/" to a kebab-case id so it appears in the browser and can be moved into folders.
```

It is only a warning, not an error, so `npm run build` exits 0 and the missing slide is easy to miss. Use a Latin kebab-case id (`peticha`, `cover`, `q2-roadmap`) and put the Hebrew in `meta.title` and in the page content, where it belongs.

**Do not over-apply this rule.** It is specific to slide folder ids. Verified as NOT affected:

| Surface | Hebrew allowed? | Why |
| --- | --- | --- |
| `slides/<id>/` folder name | No | Filtered by `SLIDE_ID_RE` at discovery |
| `meta.title` | Yes | Free text, never used as an id |
| `themes/<id>.md` file name | Yes | The theme id is `path.basename(file, '.md')` with no ASCII filter, and theme routes are built with `encodeURIComponent(id)` |
| Theme frontmatter values (`name:`, `description:`) | Yes | Only the frontmatter KEY is matched against `/^([A-Za-z0-9_-]+)\s*:/`; the value is free text |
| Slide body copy | Yes | It is just JSX text |

### Hebrew and RTL

For any Hebrew or bilingual deck, load [./rules/hebrew-rtl.md](./rules/hebrew-rtl.md) first. It covers:

- Hebrew Google Fonts (Heebo, Rubik, Assistant, Noto Sans Hebrew) loaded by head injection
- The `<html dir="rtl">` set-up vs per-page `dir` attribute (the canvas root is the page component, not the document)
- CSS logical properties (`paddingInlineStart`, `marginInlineStart`, `insetInlineStart`) instead of `paddingLeft`/`marginLeft`/`left`
- `flexDirection: 'row-reverse'` vs setting `dir` on the flex container
- Bidirectional text with `<bdi>` for mixed Hebrew + Latin (brand names, code, URLs)
- Hebrew-aware type scale tuning (Hebrew renders ~10-15% wider per character at the same px size)
- Hebrew `DesignSystem.fonts.display` and `fonts.body` choices that work at hero size
- RTL-aware theme markdown patterns under `themes/`
- Hebrew typography pitfalls: nikkud rendering, sofit letter spacing, hyphen vs maqaf

### Slide authoring (file contract, canvas, type scale, themes, design tokens)

For the technical reference on what goes inside `slides/<id>/index.tsx`, load [./rules/slide-authoring.md](./rules/slide-authoring.md). This is the same content the upstream `slide-authoring` skill exposes, with Hebrew/RTL pointers inline. It covers the file contract, the 1920×1080 canvas math, the type scale, spacing, visual direction, themes, the `DesignSystem` design-tokens object, the starter template, assets, image placeholders, and the repeated-elements rule.

Upstream has since split its own `slide-authoring` skill into per-primitive reference files. Three of them cover primitives this skill does not restate, so read them upstream rather than reimplementing by hand:

| Upstream reference | Primitive | Why it matters in a Hebrew deck |
| --- | --- | --- |
| [`references/steps.md`](https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/skills/slide-authoring/references/steps.md) | `<Steps>` / `<Step>` stepped reveals | Staged reveals let you keep each Hebrew beat short instead of overflowing one dense page |
| [`references/page-numbers.md`](https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/skills/slide-authoring/references/page-numbers.md) | `useSlidePageNumber()` | Read `{ current, total }` from the hook instead of hardcoding a footer counter; wrap the Latin digits in `<bdi>` inside an RTL footer |
| [`references/morph.md`](https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/skills/slide-authoring/references/morph.md) | `MorphElement` shared-element ("magic move") transitions | Morph interpolates position and size across the cut, so an element that moves between an LTR and an RTL page needs the same `id` on both |

### Drafting a new deck

When the user asks for "make slides about X" / "create a presentation" / "draft a deck", load [./rules/create-slide.md](./rules/create-slide.md) for the workflow: theme picker, the four scoping questions (aesthetic direction, page count, text density, motion), slide id selection, structure planning, visual commitment, write, self-review, hand off.

### Creating a theme

When the user asks to "create a theme" / "make a theme called X" / "extract a theme from this slide", load [./rules/create-theme.md](./rules/create-theme.md). A theme is a **two-file bundle**: `themes/<id>.md` (Palette / Typography / Layout / Fixed components / Motion / Aesthetic) plus a paired demo module `themes/<id>.demo.tsx` (`.jsx`, `.ts` and `.js` are also accepted), a runnable 2-3 page mini-slide that the dev UI's Themes panel renders as the live preview. Writing only the markdown leaves the theme with a dead preview card.

### Applying inspector comments

When the user has clicked on a rendered page in the dev server and added comments (via the in-browser inspector tool), and asks to "apply comments" / "process slide comments", load [./rules/apply-comments.md](./rules/apply-comments.md). It covers the `{/* @slide-comment */}` marker format, the detection regex, the base64url-decoded payload, and the apply-in-reverse-line-order procedure.

### Resolving deictic references ("this page", "the slide I'm on")

When the user references the current slide without naming it, load [./rules/current-slide.md](./rules/current-slide.md) **first**. It explains how to read the live cursor at `node_modules/.open-slide/current.json`, what fields are in it, and the staleness rules. **Re-read it on every deictic turn**, the user navigates between turns, so a value you read earlier is almost certainly stale.

### Speaker notes go in the `notes` export, never in a markdown file

open-slide has built-in speaker notes, and coding agents routinely miss them: asked for a talk track, an agent writes `script.md` or `notes.md` next to the slide, which the runtime never reads. The presenter view stays empty and the user assumes the feature is missing.

Notes are an optional `notes` export in `slides/<id>/index.tsx`, **index-aligned** with the default page array. `SlideModule` declares it as `notes?: (string | undefined)[]`. Use `undefined` to skip a page rather than shifting the array:

```tsx
const Cover: Page = () => <div dir="rtl">שלום</div>;
const Agenda: Page = () => <div dir="rtl">סדר היום</div>;

export const notes: (string | undefined)[] = [
  'פתחו בציטוט של הלקוח, ואז הציגו את עצמכם.',
  undefined,
  `שלושה עמודי תווך, פסקה לכל אחד.
עצרו לשאלות לפני המעבר.`,
];

export default [Cover, Agenda, Pillars];
```

Rules:

- One entry per page, in the same order as the default export. A missing entry silently misaligns every note after it.
- Multi-line notes work; use a template literal.
- **`notes` must be an ARRAY LITERAL.** The dev server writes notes back into your source (`PUT /__notes`), and its parser rejects anything it cannot edit in place: a computed array, a spread, an imported constant, or a `.map()` all return `422 \`notes\` export is not an array literal` and permanently break editing notes from the drawer. Write the array out by hand.
- **Hebrew notes render LTR-aligned and you cannot fix it from the slide.** The notes drawer and presenter pane set no `dir`, so Hebrew notes inherit the viewer chrome's direction. There is no author-side hook for this. Keep notes short and hard-wrap them yourself so the ragged alignment stays readable.
- Never deliver a speech script as a separate file. If the user asks for "תסריט" / "a script" / "speaker notes", it goes in this export.

**Stable ships the feature without documentation.** The `notes` runtime is present in 1.19.1, but none of the five skills bundled with that release documents it. The word "notes" does occur in them, in a table column header, a grid-overlay bullet and a line about inspector markers, but never in reference to this export. So `sync:skills` will not teach an agent the feature exists. That gap is the reason this section exists.

### Dev-server surfaces worth knowing

These are viewer features, not authoring APIs, but users ask about them and an agent that knows they exist avoids rebuilding them by hand:

| Surface | What it does |
| --- | --- |
| Notes drawer / presenter view | Renders the `notes` export above. Present mode shows notes beside the timer. |
| Command menu | Keyboard-driven palette for jumping between slides and actions. |
| Presenter-window deck switching | The presenter window can switch decks without dropping the presenter session. |
| Themes panel | Renders each theme's `<id>.demo.tsx`. A theme with no demo file shows an empty card. |
| `allowedHosts` | `open-slide.config.ts` accepts `allowedHosts?: string[] \| true`, needed when serving the dev server through a tunnel or a non-localhost host. |

### Vertical budget reminder (the #1 source of broken slides)

Anything below 1080px on the canvas is silently cropped. Before writing any page, sum:

```
(font_size × line_height × number_of_lines) + gaps + (2 × padding) ≤ 1080
```

If you're tight, **split into two pages**. Never use `overflow: auto/scroll/hidden`, negative margins, or transforms to hide overflow. The canvas does not scroll; cropped content is gone. See [./rules/slide-authoring.md](./rules/slide-authoring.md) section "Vertical budget" for the full worked example.

For Hebrew text the budget is tighter than it looks: Hebrew renders ~10-15% wider per character than Latin at the same px size, so a heading that fits English on one line at 80px may wrap to two in Heebo. Either drop the size or shorten the copy. See `hebrew-rtl.md` for the calibration table.

## Reference Links

| Source | URL | What to Check |
| --- | --- | --- |
| open-slide upstream repo | https://github.com/1weiho/open-slide | Latest API, breaking changes, version |
| open-slide docs / homepage | https://open-slide.dev | Install, demos |
| @open-slide/cli on npm | https://www.npmjs.com/package/@open-slide/cli | Current CLI version |
| @open-slide/core on npm | https://www.npmjs.com/package/@open-slide/core | Current runtime version, exports |
| MDN: CSS logical properties | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values | RTL-aware CSS, `inset-inline-start`, `padding-inline` |
| MDN: `<bdi>` element | https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi | Bidi isolation for mixed Hebrew+Latin |
| Google Fonts (Hebrew subset) | https://fonts.google.com/?subset=hebrew | Hebrew web fonts: Heebo, Rubik, Assistant, Noto Sans Hebrew |
| Upstream slide-id regex (`slide-ops.ts`) | https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/src/editing/slide-ops.ts | `SLIDE_ID_RE`, whether non-ASCII slide folder ids are still filtered out |
| Upstream webfont loading rules | https://raw.githubusercontent.com/1weiho/open-slide/7fbd1ea84cf1ee7cd701cc9fe59bff3e4e0148c5/packages/core/skills/slide-authoring/references/webfonts.md | The supported way to load a webfont (head injection, slide-keyed id) |

## Recommended MCP Servers

None directly, since open-slide has no API surface. The skill is purely client-side authoring guidance.

## Gotchas

These are the failure modes a coding agent hits when authoring an open-slide deck in Hebrew without this skill. They are NOT user errors.

1. **Using `paddingLeft` and `marginLeft` everywhere.** In LTR these read as "from the start of the line". In RTL they're literally on the wrong side of the canvas. Use `paddingInlineStart` / `paddingInlineEnd` (or shorthand `paddingInline`) so the layout flips automatically when `dir="rtl"` is set on the canvas root. The same applies to `left` / `right` (use `insetInlineStart` / `insetInlineEnd`) and `text-align: left/right` (use `text-align: start/end`).

2. **Setting `direction: 'rtl'` on `<html>` or globally.** open-slide's canvas root is the page component, not the HTML document. The dev server chrome (file rail, navigation arrows) is upstream UI in English; flipping the document direction breaks it. Set `dir="rtl"` on the page component's root `<div>` only. The Inspector and presenter mode keep their LTR layout; only your slide content flips. There is no Hebrew option to switch them to either: `OpenSlideConfig.locale` is deprecated in favour of the slide UI's language switcher, and the shipped UI locales are `en`, `zh-TW`, `zh-CN` and `ja` only. Tell the user the authoring chrome stays English; only the deck is Hebrew.

3. **Naming the slide folder in Hebrew.** It is the natural thing to do on a Hebrew deck and it silently loses the slide: discovery filters folder names through `/^[a-z0-9_-]+$/i` and only warns. Latin kebab-case id, Hebrew in `meta.title`. See "Slide folder ids must be ASCII" above.

4. **Loading the Hebrew webfont from inside the page component.** A `<style>@import ...</style>` or a `<link>` rendered inside a `Page` re-registers the whole `@font-face` set once per mounted page, and the home page, the thumbnail rail, the overview grid and the PDF print root all mount every page at the same time. Upstream's rule is to inject the stylesheet once into `<head>` from module top level of `index.tsx`, with an element id keyed to the slide (`osd-webfont-<slide-id>`) so one slide's fonts cannot suppress another's. See `hebrew-rtl.md` section 3.

5. **The default display font is a serif with no Hebrew glyphs.** `defaultDesign.fonts.display` is `Georgia, "Times New Roman", serif` and `fonts.body` is `-apple-system, BlinkMacSystemFont, "Inter", system-ui, sans-serif`. Georgia carries no Hebrew, so an unstyled Hebrew hero silently falls back to whatever system serif the OS picks (David / Times-like). That, not the body stack, is the real cause of the "my Hebrew deck renders in a dated serif" symptom. For decks that ship beyond a dev machine, load Heebo or Rubik via Google Fonts (`css2`, no `subset` parameter, it is ignored) and put them first in the stack with the system stack as fallback. See `hebrew-rtl.md`.

6. **Same type scale as English.** 140-200px hero in Inter looks tight at the lower bound (140) for Hebrew, because Hebrew uppercase-equivalent (Hebrew has no case) sits at the cap-height-equivalent of large-x-height Latin fonts. Hebrew at 140px reads like Latin at 120px. Either bump the hero to 160-220px for Hebrew, or drop the line count.

7. **Mixing Hebrew and English without `<bdi>`.** A bullet like "השתמשו ב-React Router" with mixed Hebrew + Latin tokens often renders the punctuation in the wrong place because the browser's bidi algorithm doesn't always isolate the Latin span. Wrap mixed runs in `<bdi>`: `השתמשו ב-<bdi>React Router</bdi>`. Critical for code identifiers, brand names, and URLs inside Hebrew copy.

8. **`array.map` for visually repeated cards.** The upstream rule (one component instantiated per item, NOT `map` over a data array) applies in Hebrew too. The reason is the inspector: `map` shares one source location per item, so editing one Hebrew label changes all rendered cards. Define `<Card />` once, instantiate it three times explicitly. See `slide-authoring.md`.

9. **Ignoring `node_modules/.open-slide/current.json`.** When the user says "תקן את הכותרת בעמוד הזה" ("fix the heading on this page"), the deictic "הזה" maps exactly the same way the English "this" does, you must read `current.json` to find which slide they're on. Re-read it on every turn. See `current-slide.md`.

10. **Same vertical budget at the bumped Hebrew type scale.** A cover that fits a 3-line LTR hero at 200px (3 × 200 × 0.95 = 570px) will overflow if you keep 3 lines AND bump to 232px for Hebrew (3 × 232 × 0.95 = 661px), once padding and subtitle are added you blow past 1080. Either stay at 1-2 lines for the Hebrew hero, or recompute the full budget with the bumped values before committing. Splitting is always the right answer.

11. **Mis-translated technical terms inside the slide copy.** Translation services and well-meaning agents render English dev terminology into Hebrew that means something else. Common traps:
   - **"themes" → "ערכאות"** (literally "court instances", a legal term). Correct: **"ערכות נושא"** (theme sets) or **"תמות"** (transliteration).
   - **"resolution" (as in "resolving a reference") → "רזולוציית"** (means screen/image resolution). Correct: **"פיענוח"** (decoding) or **"זיהוי"** (identification).
   - **"authoring" → "אוטרינג"** (English transliteration, sounds techy and wrong). Correct: **"כתיבה"** or **"יצירה"**.
   - **"tuned scale" → "סקאלה מכוונת"** (literal but robotic). Better: **"סקאלה מותאמת"** (adapted scale).
   Always verify Hebrew technical terms with a native speaker or a Hebrew dev community reference before shipping.

12. **Pairing brand colors as a gradient can produce an off-brand intermediate hue.** A linear gradient from Israeli Blue (`#003286`) to YooTech Magenta (`#bc46a2`) blends through saturated purple, which is NOT in the agentskills.co.il palette. If a project has a brand palette, audit each combination: hold solid bold marks for primary brand colors, and only gradient between adjacent hues (e.g., Israeli Blue → Israeli Blue Light) or fade to neutral (cream / navy). One confident solid usually beats a contrived gradient.

13. **Audit your own copy for bidi traps. Recursively.** A skill that warns about missing `<bdi>` will frequently MISS its own `<bdi>` calls inside Hebrew sentences ("השתמשו ב-RTL" needs `<bdi>RTL</bdi>`). When writing prose, slides, or examples, manually scan every Latin token embedded in Hebrew runs and wrap it. Code identifiers, brand names, version numbers, file extensions, all need isolation. The author writing the rule is not exempt from the rule.

## Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Hebrew text renders left-aligned | Page root is missing `dir="rtl"` | Add `dir="rtl"` to the page's outer `<div>`, or set `direction: 'rtl'` in its inline style |
| Hebrew heading renders in a serif when you wanted sans | Verified cause: `defaultDesign.fonts.display` is `Georgia, "Times New Roman", serif`, and Georgia has no Hebrew glyphs, so the OS substitutes a system Hebrew serif | Set `fonts.display` (not just `fonts.body`) to a Hebrew family and load it via head injection |
| Editing a note from the drawer fails, or the drawer stops saving | The `notes` export is not an array literal, so the dev server's write-back returns `422 \`notes\` export is not an array literal`. A computed array, a spread, an import or a `.map()` all trigger it | Write `notes` out as a plain array literal |
| Hebrew speaker notes are left-aligned in the presenter pane | The notes drawer and presenter set no `dir`, so notes inherit the viewer chrome's direction. There is no author-side fix | Keep notes short and hard-wrap them; this is viewer behaviour, not a bug in your slide |
| A signed number reads backwards on a Hebrew slide (`-5` shows as `5-`) | The sign has no digit on its other side, so UAX#9 W4 cannot fold it into the number; it stays a neutral and jumps to the far side | Wrap the signed number in `<bdi>`. Negative KPIs are the usual miss, they look like a single number |
| Brand name like "skills-il" appears with hyphen on the wrong side | Mixed Hebrew+Latin without bidi isolation | Wrap the Latin run in `<bdi>` |
| Hero heading wraps to a second line in Hebrew but not English | Hebrew is wider per character | Either bump hero size by ~15% or shorten the title |
| `paddingLeft: 160` puts content against the right edge of an RTL slide | Physical CSS in an RTL container | Replace with `paddingInline: 160` or `paddingInlineStart: 160` |
| Inspector arrows / file rail flipped | You set `direction: 'rtl'` globally on `<html>` | Move `dir="rtl"` to the page component's root only |
| A slide you created never appears in the sidebar or the build, but nothing errors | The folder id under `slides/` is not ASCII (a Hebrew name), so discovery skipped it with a warning only | Rename the folder to Latin kebab-case; keep the Hebrew in `meta.title`. Grep the dev/build output for `Ignoring slide folder` |
| Hebrew webfont loads but the deck feels heavy on the home page or PDF export | The `@font-face` set is registered once per mounted page because the stylesheet is injected inside a `Page` component | Inject it once into `<head>` from module top level with an id keyed to the slide (`osd-webfont-<slide-id>`) |
| `npm warn EBADENGINE Unsupported engine` on `npx @open-slide/cli init` | Node older than 22.12.0 (`commander@15` requires `>=22.12.0`) | Upgrade Node to 22.12 or newer |
| `current.json` does not exist | Dev server has not been opened on a slide yet | Run `pnpm dev` and open any slide in the browser, then re-read |
| Comment markers missing after applying | Did not delete the marker line, only the text | Re-run the regex; remove the entire line including trailing `\n` |
| `@open-slide/cli init` fails with "package not found" | Network or registry issue | Verify `npm view @open-slide/cli version` returns a version; clear pnpm/npm cache |
| Type imports fail | Wrong package or stale install | Imports come from `@open-slide/core`: `import type { Page, SlideMeta, DesignSystem } from '@open-slide/core'` |

## Bundled Resources

```
rules/
├── slide-authoring.md     # File contract, canvas, type scale, themes, design tokens, assets
├── create-slide.md        # New-deck workflow: theme picker, scoping questions, structure
├── create-theme.md        # Theme markdown authoring under themes/<id>.md
├── apply-comments.md      # @slide-comment marker handling
├── current-slide.md       # Resolving "this page" via current.json
└── hebrew-rtl.md          # Hebrew fonts, bidi, logical CSS, RTL DesignSystem (the new content)
```

Load on demand based on the task. Always start with `hebrew-rtl.md` for Hebrew or bilingual content.

