You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add paragraph borders doc page and spec section linking
- New doc page for w:pBdr with live previews, implementation notes,
and rendering diagram for between-border groups
- Spec explorer supports ?section=ID&part=N for direct section lookup
- Spec references in docs link directly to the spec explorer
- Table cells now render inline markdown (code, links)
- Sidebar "new" badge support
- Add CLAUDE.md and AGENTS.md
The OOXML spec, explained by people who actually implemented it.
4
+
5
+
An interactive reference for ECMA-376 (Office Open XML) built by the [SuperDoc](https://superdoc.dev) team. Live previews are rendered with SuperDoc itself — every example on the site is a working document.
6
+
7
+
## Why This Exists
8
+
9
+
The official ECMA-376 spec is 5,000+ pages. Most of it you'll never need, and the parts you do need often omit critical rendering details that only surface when you compare against Word's actual behavior. This site fills that gap with implementation notes from building SuperDoc — a document editor that renders OOXML natively in the browser.
10
+
11
+
This is also how people discover SuperDoc. By sharing what we've learned, we position ourselves as the OOXML experts. Every page should reflect that authority: practical, specific, from-experience.
12
+
13
+
## Content Philosophy
14
+
15
+
**Write for implementers, not spec lawyers.** The audience is developers building document tools who need to know what the spec doesn't tell them.
16
+
17
+
Every doc page should answer:
18
+
1.**What does the XML look like?** — Structure tree and live examples
19
+
2.**What does Word actually do?** — Rendering behavior, especially where it diverges from the spec
20
+
3.**What will trip you up?** — Implementation notes from real experience
21
+
22
+
Keep notes concise (1-2 sentences). Lead with the insight, not the backstory. Use `app: "Word"` when the behavior is Word-specific.
23
+
24
+
## Project Structure
25
+
26
+
```
27
+
apps/
28
+
web/ React app (Vite, React Router, Tailwind)
29
+
src/data/docs.ts ← All doc pages live here (single source of truth)
mcp-server/ Cloudflare Worker — MCP server for AI spec search
33
+
packages/
34
+
shared/ Database client, embedding client, types
35
+
scripts/
36
+
ingest/ PDF → chunks → embeddings → database pipeline
37
+
db/
38
+
schema.sql PostgreSQL + pgvector schema
39
+
dev/
40
+
data/ Extracted/chunked/embedded spec content
41
+
```
42
+
43
+
## Commands
44
+
45
+
```bash
46
+
bun install # Install dependencies
47
+
bun dev # Web app at http://localhost:5173
48
+
bun dev:mcp # MCP server at http://localhost:8787
49
+
bun run build # Production build (web)
50
+
bun run typecheck # Type-check all packages
51
+
```
52
+
53
+
## Adding a Doc Page
54
+
55
+
All documentation lives in `apps/web/src/data/docs.ts` as a keyed object. Each page has a `title`, optional `badge` (OOXML element), and `content` array of typed blocks.
56
+
57
+
### Content block types
58
+
59
+
| Type | Purpose |
60
+
|------|---------|
61
+
|`heading`| Section heading (level 2, 3, or 4) |
62
+
|`paragraph`| Prose text (supports markdown links) |
63
+
|`code`| Code/structure block with optional language |
64
+
|`preview`| Live OOXML rendered by SuperDoc (editable XML + preview) |
65
+
|`note`| Implementation note (critical / warning / info / tip) with optional `app`|
66
+
|`table`| Data table with headers and rows |
67
+
68
+
### Steps
69
+
70
+
1. Add an entry to the `docs` object in `apps/web/src/data/docs.ts`
71
+
2. Add a sidebar link in `apps/web/src/components/Sidebar.tsx` under the right section
72
+
3. The page auto-routes to `/docs/{key}`
73
+
74
+
### Page structure convention
75
+
76
+
Follow this order (see existing pages for examples):
77
+
1. Intro paragraph — what the element does, one sentence on why it matters
78
+
2.**Structure** — element tree showing hierarchy and attributes
79
+
3.**Examples** — live `preview` blocks, start simple, build complexity
80
+
4.**Implementation Notes** — the real value; what the spec doesn't tell you
81
+
5.**Schema** — reference table of elements/attributes
82
+
83
+
### Writing implementation notes
84
+
85
+
-**critical** — things that will break your implementation if you get them wrong
86
+
-**warning** — non-obvious behavior that affects rendering
87
+
-**info** — good to know, won't break things
88
+
-**tip** — helpful shortcuts or techniques
89
+
90
+
Use `app: "Word"` (or `"Word, LibreOffice"`) when the behavior is application-specific. Omit `app` for universal observations.
91
+
92
+
## SuperDoc Preview Component
93
+
94
+
The `preview` block type renders XML with SuperDoc loaded from unpkg. It creates a minimal .docx in-memory (via JSZip), passes it to SuperDoc, and shows a split view: editable XML on the left, live rendering on the right.
95
+
96
+
The XML you provide is wrapped in a minimal `w:document > w:body` structure automatically. Just provide the body content (paragraphs, tables, etc.).
97
+
98
+
## MCP Server
99
+
100
+
Cloudflare Worker exposing three MCP tools for AI-powered spec search:
101
+
102
+
-`search_ecma_spec` — semantic vector search across 18,000+ spec chunks
103
+
-`get_section` — fetch a specific section by ID (e.g., "17.3.1.24")
104
+
-`list_parts` — browse the spec structure
105
+
106
+
Uses PostgreSQL with pgvector (Neon serverless in production, Docker locally).
"Borders and shading around paragraphs — side borders, between-border groups, and the space attribute.",
278
+
badge: "w:pBdr",
279
+
content: [
280
+
{
281
+
type: "paragraph",
282
+
text: "Paragraph borders (`w:pBdr`) draw border lines around paragraphs and can group consecutive paragraphs into a single bordered box. The spec reads straightforward, but Word's rendering rules have several gotchas that aren't documented.",
283
+
},
284
+
{type: "heading",level: 2,text: "Structure"},
285
+
{
286
+
type: "code",
287
+
code: `w:pPr (paragraph properties)
288
+
└── w:pBdr (paragraph borders)
289
+
├── w:top (top border)
290
+
├── w:bottom (bottom border)
291
+
├── w:left (left border)
292
+
├── w:right (right border)
293
+
├── w:between (between border — separator within groups)
text: "When consecutive paragraphs have identical border definitions AND include a `w:between` element, Word groups them into a single bordered box. The between border draws as a horizontal separator between group members.",
322
+
},
323
+
{
324
+
type: "preview",
325
+
title: "Two paragraphs grouped with a between border",
<w:r><w:t>Second paragraph — between border separates them.</w:t></w:r>
349
+
</w:p>`,
350
+
},
351
+
{type: "heading",level: 2,text: "Nil/None Between — Grouping Without a Separator"},
352
+
{
353
+
type: "paragraph",
354
+
text: 'Setting `w:between` to `val="nil"` or `val="none"` does NOT mean "don\'t group." It means "group these paragraphs but don\'t draw a separator." The result is a single continuous bordered box with no divider between paragraphs.',
355
+
},
356
+
{
357
+
type: "preview",
358
+
title: "Grouped paragraphs with no separator (nil between)",
text: 'Consecutive paragraphs form a group when they all have a `w:between` element AND all border properties match (top, bottom, left, right, between). Crucially, `val="nil"` or `val="none"` still triggers grouping — it means "group without a separator," not "don\'t group." If you normalize nil/none to `undefined` during parsing, you lose the grouping signal entirely.',
410
+
app: "Word",
411
+
},
412
+
{
413
+
type: "note",
414
+
noteType: "warning",
415
+
title: "The space attribute and unit mismatch",
416
+
text: 'The `space` attribute sets the distance (in points) between a border\'s inner edge and the text. For between borders, this padding applies on both sides — above and below. Note that `sz` uses a different unit: eighths of a point (`sz="12"` = 1.5pt). Easy to mix up since they\'re on the same element.',
417
+
app: "Word",
418
+
},
419
+
{type: "heading",level: 2,text: "Schema"},
420
+
{
421
+
type: "table",
422
+
headers: ["Element","Description"],
423
+
rows: [
424
+
["`w:top`","Top border"],
425
+
["`w:bottom`","Bottom border"],
426
+
["`w:left`","Left border"],
427
+
["`w:right`","Right border"],
428
+
["`w:between`","Border between grouped paragraphs"],
429
+
["`w:bar`","Vertical bar border (drawn outside the paragraph)"],
430
+
],
431
+
},
432
+
{
433
+
type: "table",
434
+
headers: ["Attribute","Type","Description"],
435
+
rows: [
436
+
["`w:val`","ST_Border","Border style — single, double, dashed, dotted, nil, none, etc."],
437
+
["`w:sz`","integer","Width in 1/8 of a point (e.g., 12 = 1.5pt)"],
438
+
["`w:space`","integer","Distance from text to border inner edge, in points"],
439
+
["`w:color`","hex","Border color (e.g., 000000, auto)"],
440
+
["`w:shadow`","boolean","Shadow effect on the border"],
441
+
["`w:frame`","boolean","Frame effect on the border"],
0 commit comments