@linloir/limr 0.1.0 → 1.0.1
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/README.en.md +151 -0
- package/README.md +80 -127
- package/benchmarks/results/layout.json +2593 -0
- package/benchmarks/results/markstream-code.json +1823 -0
- package/benchmarks/results/markstream-reading-repeat.json +1152 -0
- package/benchmarks/results/markstream.json +12486 -0
- package/benchmarks/results/streaming-browser.json +77 -0
- package/benchmarks/results/streaming.json +357 -0
- package/dist/base.css +93 -10
- package/dist/{chunk-7JF5HHVF.js → chunk-BG5QUQFR.js} +340 -72
- package/dist/chunk-BG5QUQFR.js.map +1 -0
- package/dist/chunk-BVHHOVMT.js +179 -0
- package/dist/chunk-BVHHOVMT.js.map +1 -0
- package/dist/chunk-FJZCQY2S.js +155 -0
- package/dist/chunk-FJZCQY2S.js.map +1 -0
- package/dist/chunk-FSI5OTBW.js +1 -0
- package/dist/{chunk-ETHLPBKQ.js → chunk-GU2RPESF.js} +238 -131
- package/dist/chunk-GU2RPESF.js.map +1 -0
- package/dist/{chunk-65V5OZ4G.js → chunk-SUAAUGAX.js} +1350 -511
- package/dist/chunk-SUAAUGAX.js.map +1 -0
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.js +15 -5
- package/dist/extensions/index.d.ts +30 -5
- package/dist/extensions/index.js +15 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +31 -8
- package/dist/{engine-43u8B4yt.d.ts → knuth-plass-C8mgFm7W.d.ts} +67 -1
- package/dist/knuth-plass-layout-CHQCWKB5.js +449 -0
- package/dist/knuth-plass-layout-CHQCWKB5.js.map +1 -0
- package/dist/react/index.d.ts +7 -5
- package/dist/react/index.js +7 -3
- package/dist/styles.css +93 -10
- package/dist/{translations-BliabrPd.d.ts → translations-BXzSwLBJ.d.ts} +49 -4
- package/docs/README.md +13 -0
- package/docs/api.md +5 -158
- package/docs/assets/rendered.svg +9 -0
- package/docs/assets/source.svg +9 -0
- package/docs/en/README.md +28 -0
- package/docs/en/ai-skills.md +57 -0
- package/docs/en/annotations.md +86 -0
- package/docs/en/api.md +115 -0
- package/docs/en/architecture.md +41 -0
- package/docs/en/extensions.md +208 -0
- package/docs/en/getting-started.md +69 -0
- package/docs/en/integration.md +154 -0
- package/docs/en/modules.md +84 -0
- package/docs/en/performance.md +93 -0
- package/docs/en/plugin-packages.md +68 -0
- package/docs/en/styles.md +99 -0
- package/docs/en/syntax.md +455 -0
- package/docs/extensions.md +5 -306
- package/docs/implementation.md +5 -76
- package/docs/performance.md +5 -81
- package/docs/screenshots/blocks.png +0 -0
- package/docs/screenshots/dark.png +0 -0
- package/docs/screenshots/desktop.png +0 -0
- package/docs/screenshots/plugin-mobile.png +0 -0
- package/docs/styles.md +5 -100
- package/docs/visual-upgrade.md +5 -145
- package/docs/zh-CN/README.md +28 -0
- package/docs/zh-CN/ai-skills.md +57 -0
- package/docs/zh-CN/annotations.md +86 -0
- package/docs/zh-CN/api.md +115 -0
- package/docs/zh-CN/architecture.md +41 -0
- package/docs/zh-CN/extensions.md +210 -0
- package/docs/zh-CN/getting-started.md +69 -0
- package/docs/zh-CN/integration.md +154 -0
- package/docs/zh-CN/modules.md +84 -0
- package/docs/zh-CN/performance.md +93 -0
- package/docs/zh-CN/plugin-packages.md +68 -0
- package/docs/zh-CN/styles.md +99 -0
- package/docs/zh-CN/syntax.md +457 -0
- package/examples/notes.tsx +18 -11
- package/examples/packages/paper-theme/index.css +25 -0
- package/examples/packages/paper-theme/package.json +7 -0
- package/examples/packages/reading-tools/index.d.ts +2 -0
- package/examples/packages/reading-tools/index.js +25 -0
- package/examples/packages/reading-tools/package.json +16 -0
- package/package.json +17 -5
- package/skills/limr-plugin-development/SKILL.md +22 -0
- package/skills/limr-plugin-development/agents/openai.yaml +4 -0
- package/skills/limr-plugin-development/assets/plugin-template/README.md +30 -0
- package/skills/limr-plugin-development/assets/plugin-template/package.json +36 -0
- package/skills/limr-plugin-development/assets/plugin-template/src/index.tsx +50 -0
- package/skills/limr-plugin-development/assets/plugin-template/src/styles.css +9 -0
- package/skills/limr-plugin-development/assets/plugin-template/tsconfig.json +20 -0
- package/skills/limr-plugin-development/assets/plugin-template/tsup.config.ts +8 -0
- package/skills/limr-plugin-development/references/contract.md +51 -0
- package/skills/limr-use/SKILL.md +23 -0
- package/skills/limr-use/agents/openai.yaml +4 -0
- package/skills/limr-use/references/integration.md +49 -0
- package/skills/limr-use/references/syntax.md +20 -0
- package/dist/chunk-65V5OZ4G.js.map +0 -1
- package/dist/chunk-7JF5HHVF.js.map +0 -1
- package/dist/chunk-ETHLPBKQ.js.map +0 -1
- package/dist/chunk-V2OG4Y3F.js +0 -1
- /package/dist/{chunk-V2OG4Y3F.js.map → chunk-FSI5OTBW.js.map} +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Application integration
|
|
2
|
+
|
|
3
|
+
> Reading note: this page uses LiMR syntax and interactive examples. Some content cannot be fully displayed by ordinary Markdown viewers such as GitHub; visit the [GitHub Pages documentation](https://linloir.github.io/LiMR/#/docs/en/integration) for the best reading experience.
|
|
4
|
+
|
|
5
|
+
[Documentation](README.md) · [简体中文](../zh-CN/integration.md)
|
|
6
|
+
|
|
7
|
+
## Choose a component
|
|
8
|
+
|
|
9
|
+
Use `Reader` for the complete experience and `Markdown` for standalone content. For a custom layout, one DocumentProvider manages one primary Markdown plus its navigation, cards, and toolbar. Passing source to Markdown inside a provider creates an independent document instead of sharing the parent state.
|
|
10
|
+
|
|
11
|
+
## Streaming content
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Markdown } from '@linloir/limr';
|
|
15
|
+
|
|
16
|
+
export function StreamingAnswer({ answer, generating }: {
|
|
17
|
+
answer: string;
|
|
18
|
+
generating: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
return <Markdown source={answer} streaming={generating} followOutput />;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The application owns transport and appends each chunk to the complete answer. Do not pass only the newest chunk as source. The final update must set streaming to false to finalize syntax and semantics. Finalization can reuse stable prefixes; definition changes and other global dependencies may still require a full parse. followOutput follows only while the reader remains near the bottom. The core engine supports append(chunk) / finish(); React props take the accumulated string.
|
|
25
|
+
|
|
26
|
+
New text and special blocks fade through opacity by default, without replaying the entrance when a paragraph finishes. Use `streamEffect={false}` to disable it or supply `className`, `keyframes`, and `timing`; reduced-motion preferences take precedence. Unclosed modules can show loading states while code/math render progressively. Reuse `children`, `renderChildren`, and `renderValue` to preserve controls and selections. See [extensions](extensions.md).
|
|
27
|
+
|
|
28
|
+
## Paragraph typography
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { useState } from 'react';
|
|
32
|
+
import { Reader, type TypographyMode } from '@linloir/limr';
|
|
33
|
+
|
|
34
|
+
export function TypographyReader({ source }: { source: string }) {
|
|
35
|
+
const [mode, setMode] = useState<TypographyMode>('standard');
|
|
36
|
+
return <>
|
|
37
|
+
<select aria-label="Typography" value={mode}
|
|
38
|
+
onChange={event => setMode(event.target.value as TypographyMode)}>
|
|
39
|
+
<option value="standard">Standard</option>
|
|
40
|
+
<option value="knuth-plass">Knuth–Plass</option>
|
|
41
|
+
<option value="justify">Justified</option>
|
|
42
|
+
</select>
|
|
43
|
+
<Reader source={source} typography={mode} />
|
|
44
|
+
</>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`standard` is the default; `justify` uses browser justification; `knuth-plass` enables the paragraph optimizer. `knuthPlass` configures TeX parameters and hyphenation. Streaming tails participate in KP once stable. See [styles](styles.md) and [performance](performance.md) for behavior and measurement boundaries.
|
|
49
|
+
|
|
50
|
+
## Nested scroll containers
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { useState } from 'react';
|
|
54
|
+
import { Reader } from '@linloir/limr';
|
|
55
|
+
|
|
56
|
+
export function ScrollPanel({ source }: { source: string }) {
|
|
57
|
+
const [scrollRoot, setScrollRoot] = useState<HTMLDivElement | null>(null);
|
|
58
|
+
return <div ref={setScrollRoot} style={{ height: '80vh', overflowY: 'auto' }}>
|
|
59
|
+
<Reader source={source} scrollRoot={scrollRoot} scrollOffset={32} />
|
|
60
|
+
</div>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Window scrolling needs no scrollRoot. Set scrollOffset for a fixed header and --limr-sticky-top for sticky navigation. Give Reader a new React key when switching to an independent document, plus a stable documentId unique on the page, to recreate task and interaction state.
|
|
65
|
+
|
|
66
|
+
## Custom composition
|
|
67
|
+
|
|
68
|
+
This is a fixed desktop layout. Prefer Reader for responsive sidebar switching, collapsible contents, and automatic height. With your own breakpoints, decide when to mount the sidebar or overlay.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { DocumentProvider, Markdown, TableOfContents, AnnotationSidebar,
|
|
72
|
+
SelectionToolbar } from '@linloir/limr';
|
|
73
|
+
|
|
74
|
+
export function DesktopReader({ source }: { source: string }) {
|
|
75
|
+
return <DocumentProvider source={source}>
|
|
76
|
+
<div style={{ display: 'grid', gridTemplateColumns: '180px minmax(0, 1fr) 280px', gap: 24 }}>
|
|
77
|
+
<TableOfContents style={{ position: 'sticky', top: 24, alignSelf: 'start', maxHeight: '80vh' }} />
|
|
78
|
+
<main className="limr-reader-content" style={{ minHeight: '70vh', overflow: 'clip' }}>
|
|
79
|
+
<Markdown />
|
|
80
|
+
</main>
|
|
81
|
+
<AnnotationSidebar />
|
|
82
|
+
</div>
|
|
83
|
+
<SelectionToolbar />
|
|
84
|
+
</DocumentProvider>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Keep body and synchronized sidebar in the same scroll container and unscaled coordinate system. A grid row or the host supplies sidebar height. Do not grow the page to the total card height or add a separate scroll container to the synchronized list. Long card bodies can scroll independently. For a drawer or ordinary list, use AnnotationSidebar with sync="none" and let the host constrain height and overflow.
|
|
89
|
+
|
|
90
|
+
For an overlay layout, mount Markdown, AnnotationOverlay, and SelectionToolbar under the same provider. The two floating components accept portalRoot; Reader does not expose that prop directly, so compose components when it is needed.
|
|
91
|
+
|
|
92
|
+
## Controlled data and document switching
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { useState } from 'react';
|
|
96
|
+
import { Reader, type Annotation } from '@linloir/limr';
|
|
97
|
+
|
|
98
|
+
export function SavedArticle({ id, source }: { id: string; source: string }) {
|
|
99
|
+
const [annotations, setAnnotations] = useState<readonly Annotation[]>([]);
|
|
100
|
+
return <Reader key={id} documentId={id} source={source}
|
|
101
|
+
annotations={annotations} onAnnotationsChange={setAnnotations} />;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This illustrates state for one article. In an application, load/store annotations by article ID and replace the array on navigation; a React key does not clear host state. Omit annotations for internal state. When supplied, accept edits through onAnnotationsChange and pass the new array back, or editing throws. Do not mutate arrays or data in place. See [annotations and selection](annotations.md) for persistence and relocation rules.
|
|
106
|
+
|
|
107
|
+
## Global translation mode
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useState } from 'react';
|
|
111
|
+
import { Reader, type TranslationMode } from '@linloir/limr';
|
|
112
|
+
|
|
113
|
+
export function BilingualArticle({ source }: { source: string }) {
|
|
114
|
+
const [mode, setMode] = useState<TranslationMode | undefined>();
|
|
115
|
+
return <Reader source={source}
|
|
116
|
+
parameters={{ translationMode: mode, onTranslationModeChange: setMode }} />;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Parameters update views without reparsing. Other plugins can register parameters as described in [extension development](extensions.md).
|
|
121
|
+
|
|
122
|
+
## SSR and client boundaries
|
|
123
|
+
|
|
124
|
+
Place interactive components inside your framework's client component boundary (for example, 'use client' where required) and let the framework arrange SSR/hydration. Avoid window/document at module initialization. Keep initial source, documentId, and configuration consistent between server and client.
|
|
125
|
+
|
|
126
|
+
Synchronous SSR falls back to formula source until math is loaded. To include complete KaTeX output in the first HTML, preload it on the server:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { renderToString } from 'react-dom/server';
|
|
130
|
+
import { Markdown, preloadMath } from '@linloir/limr';
|
|
131
|
+
|
|
132
|
+
export async function renderArticle(source: string) {
|
|
133
|
+
await preloadMath();
|
|
134
|
+
return renderToString(<Markdown source={source} documentId="server-article" />);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
React streaming SSR can also handle Suspense. Shiki adds highlighting on the client; SSR already includes code text. Mermaid emits a loading state on the server and fades in after client rendering. KP measures fonts and typesets on the client; SSR retains ordinary text layout. Annotation load requests start when a card is shown on the client. Put preloaded data in annotation.data if needed. The host must still serve CSS and fonts.
|
|
139
|
+
|
|
140
|
+
## Existing plugins and themes
|
|
141
|
+
|
|
142
|
+
Import a factory from a real plugin package and create a stable plugins array outside rendering or with useMemo. Import plugin CSS explicitly as documented by its author. Themes are selected with appearance, not passed through plugins. See [plugin packages](plugin-packages.md) for installation and local tgz consumption.
|
|
143
|
+
|
|
144
|
+
## Troubleshooting
|
|
145
|
+
|
|
146
|
+
| Symptom | Check |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| Missing layout or malformed formulas | Import styles or base, plus math.css |
|
|
149
|
+
| Default modules disappear after adding a plugin | Use plugins to append, rather than replacing extensions |
|
|
150
|
+
| Annotation edits do not persist | Return a new array from the controlled callback |
|
|
151
|
+
| Body and navigation disagree | A Markdown source prop may have created a second provider |
|
|
152
|
+
| Nested scrolling targets the wrong place | Pass the actual scrolling DOM element as scrollRoot |
|
|
153
|
+
| Portal colors differ from the reader | Scope CSS by data-limr-appearance |
|
|
154
|
+
| Streaming repeatedly reparses everything | Keep extensions/plugins stable and check document-scoped transforms |
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Built-in modules
|
|
2
|
+
|
|
3
|
+
> Reading note: this page uses LiMR syntax and interactive examples. Some content cannot be fully displayed by ordinary Markdown viewers such as GitHub; visit the [GitHub Pages documentation](https://linloir.github.io/LiMR/#/docs/en/modules) for the best reading experience.
|
|
4
|
+
|
|
5
|
+
[Documentation](README.md) · [简体中文](../zh-CN/modules.md)
|
|
6
|
+
|
|
7
|
+
## What is a module?
|
|
8
|
+
|
|
9
|
+
LiMR's base parser understands CommonMark: headings, paragraphs, lists, links, images, and code fences. A **module** is a configuration object that adds capabilities, such as formulas, video rendering, or citation cards. Its public TypeScript type is `RendererExtension`, so the API also calls it an extension.
|
|
10
|
+
|
|
11
|
+
A module can combine parsing rules, React components, annotation types, toolbar actions, and default parameters. It need not include all of them. For example, `math` adds formula syntax and rendering, `code-highlight` colors existing code fences, and `citation` provides both reference syntax and annotation cards.
|
|
12
|
+
|
|
13
|
+
A **plugin** usually means a module added by your application or supplied by a third-party package. It uses the same interface as built-in modules. An npm package distributes code and can export several modules. Passing a module to `plugins` appends it to the existing configuration.
|
|
14
|
+
|
|
15
|
+
## Functions, module names, and tag names
|
|
16
|
+
|
|
17
|
+
Consider video:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Reader, standard, replaceExtension, video } from '@linloir/limr';
|
|
21
|
+
|
|
22
|
+
const videoModule = video({ loading: 'click' });
|
|
23
|
+
// videoModule.name === 'video'
|
|
24
|
+
const extensions = replaceExtension(standard(), videoModule);
|
|
25
|
+
|
|
26
|
+
export function Article() {
|
|
27
|
+
return <Reader extensions={extensions} source={'<video src="/lesson.mp4" />'} />;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Form | Where it belongs | Meaning |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `video` / `video(...)` | Application JavaScript / TypeScript | The imported function / a call that creates a module configuration object |
|
|
34
|
+
| `videoModule` | Application code | Your variable name for that object; you can rename it |
|
|
35
|
+
| `'video'` | The module object's `name` property | A stable identifier used to distinguish, replace, and remove modules |
|
|
36
|
+
| `<video … />` / `<md-video … />` | Markdown content | Two tags registered by the module, using the same video renderer |
|
|
37
|
+
| `::video{…}` | Markdown content | A registered directive; a `:::video` container also works |
|
|
38
|
+
|
|
39
|
+
The names need not match. The code-highlighting factory is `codeHighlight()`, its module name is `'code-highlight'`, and its content uses ordinary code fences without a `<code-highlight>` tag. Writing `video()` or the string `'video'` inside Markdown does not enable a module. Pass the returned object to `plugins`, not a string.
|
|
40
|
+
|
|
41
|
+
`video({ loading: 'click' })` sets module defaults for that Reader. A `<video loading="eager" … />` tag overrides the setting for one video. Available options vary by module; see [default syntax](syntax.md).
|
|
42
|
+
|
|
43
|
+
## Default modules included with LiMR
|
|
44
|
+
|
|
45
|
+
These modules ship in `@linloir/limr`; no separate npm installations are required. `standard()` returns the following module objects with their default settings.
|
|
46
|
+
|
|
47
|
+
| Factory function | Object's `name` | Capability and content syntax |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `gfm()` | `gfm` | Tables, `~~strikethrough~~`, tasks, autolinks, `[^id]` footnote parsing |
|
|
50
|
+
| `math()` | `math` | `$…$` inline and `$$…$$` display math; also import math.css |
|
|
51
|
+
| `codeHighlight()` | `code-highlight` | Shiki colors for code fences with a language identifier |
|
|
52
|
+
| `definition()` | `definition` | `<definition>` cards with a title and Markdown body |
|
|
53
|
+
| `question()` | `question` | `<question>` exercises with choices, hints, answers, and local scoring |
|
|
54
|
+
| `extras()` | `extras` | `<extras>` / `<extra>` further-reading links |
|
|
55
|
+
| `video()` | `video` | `<video>` / `<md-video>` native video, YouTube, and Bilibili embeds |
|
|
56
|
+
| `figures()` | `figures` | `<figures>` / `<figure>` responsive groups, single figures, and captions |
|
|
57
|
+
| `citation()` | `citation` | `<citation>` source definitions and `<ref>` markers with cards |
|
|
58
|
+
| `translations()` | `translations` | `<translations>` original, translated, and parallel views |
|
|
59
|
+
| `mermaid()` | `mermaid` | Mermaid fences / `::mermaid`, image/source, zoom and copy |
|
|
60
|
+
| `callout()` | `callout` | Obsidian callout types, aliases and nested quotes / `::callout` |
|
|
61
|
+
| `inlineHighlights()` | `inline-highlights` | `==text==` and `::highlight`, blue read-only highlights |
|
|
62
|
+
| `footnotes()` | `footnotes` | `[^id]` / `^[body]`, comment markers and cards; `::footnote` / `::footnote-ref` |
|
|
63
|
+
|
|
64
|
+
Import these functions from `@linloir/limr` or `@linloir/limr/extensions`. `standard()` is a convenience function returning an array, not an additional module.
|
|
65
|
+
|
|
66
|
+
`Reader`, standalone `Markdown`, and `DocumentProvider` automatically use `standard()` when `extensions` is omitted. Your first `<Reader source={source} />` therefore supports these capabilities. Components inside a shared Provider inherit its configuration. Core-only `createDocument()` starts with CommonMark; extensions must be explicit, and it does not display React UI.
|
|
67
|
+
|
|
68
|
+
Highlight, copy, and annotation deletion are built-in toolbar actions. Default modules recognize `==text==` and Markdown footnotes. Source-authored highlights offer copying but no toolbar editing. The Playground's editable margin notes, reading notes, questions, footnote annotations, and simulated assistant come from example plugins and **are not automatically installed in every Reader**. The `question()` exercise module is also separate from question annotations.
|
|
69
|
+
|
|
70
|
+
## Choosing a configuration
|
|
71
|
+
|
|
72
|
+
You do not need to call `standard()` when the defaults suffice. For customization, distinguish three operations:
|
|
73
|
+
|
|
74
|
+
| Goal | Configuration | Result |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| Add your own capability | `plugins={[myPlugin]}` | Keep default modules and append your plugin object |
|
|
77
|
+
| Keep only selected capabilities | `extensions={[gfm(), math()]}` | Replace the complete default module list; CommonMark remains available |
|
|
78
|
+
| Configure a built-in module | `extensions={replaceExtension(standard(), video({ loading: 'click' }))}` | Replace video by module `name`, preserving the other modules |
|
|
79
|
+
|
|
80
|
+
To remove one module, filter the array: `standard().filter(module => module.name !== 'video')`. Use `extensions={[]}` when extension syntax is unnecessary.
|
|
81
|
+
|
|
82
|
+
Do not append `plugins={[video()]}` while the default video module is present: duplicate module names, tags, or directives throw. Without the corresponding module, unknown tags render as text, unknown directives may retain only their inner Markdown, and unknown annotation data remains with generic fallback UI. See [architecture and behavior](architecture.md) for the precise boundaries.
|
|
83
|
+
|
|
84
|
+
Next, try the [syntax examples](syntax.md), create modules with [extension development](extensions.md), or distribute them using [plugin packages](plugin-packages.md).
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Performance and limits
|
|
2
|
+
|
|
3
|
+
> Reading note: this page uses LiMR syntax and interactive examples. Some content cannot be fully displayed by ordinary Markdown viewers such as GitHub; visit the [GitHub Pages documentation](https://linloir.github.io/LiMR/#/docs/en/performance) for the best reading experience.
|
|
4
|
+
|
|
5
|
+
[Documentation](README.md) · [简体中文](../zh-CN/performance.md)
|
|
6
|
+
|
|
7
|
+
The first sections retain the repository's **2026-10-12 historical measurements** for comparison. The **2026-10-13** section below records this optimization's measurements. Neither set is a performance guarantee.
|
|
8
|
+
|
|
9
|
+
Environment: Intel Core i7-13700, WSL2/Linux, Node 20.19.5, Chromium 156.0.8078.4, React 19 production. Baseline: react-markdown 10.1.0 + remark-gfm 4.0.1.
|
|
10
|
+
|
|
11
|
+
## Browser
|
|
12
|
+
|
|
13
|
+
Both render equivalent GFM with the same typography, body only, excluding Reader navigation/sidebar. Values are medians of three isolated pages after warmup and font readiness, alternating renderer order. Timing includes synchronous React commits and forced layout, excluding network, asynchronous heavy resources, and idle frame time.
|
|
14
|
+
|
|
15
|
+
| Scenario | LiMR | react-markdown |
|
|
16
|
+
| --- | ---: | ---: |
|
|
17
|
+
| 87,381 characters, 200 sections, initial mount | 126.1 ms | 134.0 ms |
|
|
18
|
+
| 877,782 characters, 2,000 sections, initial mount | 1,345.4 ms | 1,437.6 ms |
|
|
19
|
+
| 34,860 characters, 138 streaming commits, total synchronous time | 306.1 ms | 1,864.1 ms |
|
|
20
|
+
| Same stream, per-commit P95 | 2.9 ms | 23.7 ms |
|
|
21
|
+
|
|
22
|
+
The final full parse is included. P95 does not mean every frame stays below 16ms. Raw data: [browser.json](../../benchmarks/results/browser.json).
|
|
23
|
+
|
|
24
|
+
## Selection and SSR
|
|
25
|
+
|
|
26
|
+
Historical long-document selection paint latency P95 was 25.2–28.5ms; code was 18.9–19.4ms. This measures selectionchange to paint-layer updates, not hardware input-to-display latency. The same automated drag took 3.23s in LiMR and 1.52s in the baseline; similar rAF intervals do not imply identical processing cost. Data: [selection.json](../../benchmarks/results/selection.json).
|
|
27
|
+
|
|
28
|
+
| Node scenario | LiMR | Baseline |
|
|
29
|
+
| --- | ---: | ---: |
|
|
30
|
+
| 87,381 characters, SSR | 123.85 ms | 135.80 ms |
|
|
31
|
+
| 877,782 characters, SSR | 5,508.29 ms | 2,720.70 ms |
|
|
32
|
+
| 34,860 characters, 137 core appends/finalization | 164.04 ms | 2,673.74 ms |
|
|
33
|
+
|
|
34
|
+
Large-document SSR varied substantially: LiMR samples were 2.64–9.10s and baseline 2.70–5.03s. These results show no SSR advantage. The append baseline reparses the entire prefix each time. Data: [node.json](../../benchmarks/results/node.json).
|
|
35
|
+
|
|
36
|
+
## Practical limits
|
|
37
|
+
|
|
38
|
+
Keeping the full DOM supports search, selection, and printing, but initial long-document rendering still costs time. Incremental parsing reuses stable prefixes; long tail blocks, document-scoped transforms, definition changes, middle edits, and finalization can remain expensive. Keep extensions/plugins stable and limit highlighting languages/length when appropriate.
|
|
39
|
+
|
|
40
|
+
The historical measurements above exclude full Reader layouts; the newer KP comparison below includes one complete Reader workload. Neither set covers large formula collections, Shiki cold starts, arbitrary dense annotations/plugins, or physical mobile devices. Installed font size also differs from browser transfers; see [styles and fonts](styles.md).
|
|
41
|
+
|
|
42
|
+
## Reproduce in the repository
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
NODE_ENV=production pnpm bench
|
|
46
|
+
pnpm exec vite build
|
|
47
|
+
pnpm exec vite preview --host 127.0.0.1 --port 4174
|
|
48
|
+
# In another terminal:
|
|
49
|
+
pnpm bench:browser
|
|
50
|
+
pnpm bench:selection
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Avoid concurrent builds/benchmarks. Scripts write benchmarks/results. Reevaluate using your hardware, content, and built version rather than treating historical numbers as current-package acceptance.
|
|
54
|
+
|
|
55
|
+
## 2026-10-13: streaming and layout measurements
|
|
56
|
+
|
|
57
|
+
This compares the working implementation with pre-change commit `ebab991` in one process, using the same dependencies, UTF-16 input and chunk boundaries. Five warmed trials alternate order; values are the sample with median total time. Core append and finalization only: React, layout, async highlighting and network are excluded. Environment and raw samples: [streaming.json](../../benchmarks/results/streaming.json).
|
|
58
|
+
|
|
59
|
+
| Scenario | Characters | Before total | After total |
|
|
60
|
+
| --- | ---: | ---: | ---: |
|
|
61
|
+
| GFM | 34,860 | 139.22ms | 89.07ms |
|
|
62
|
+
| Default modules | 34,860 | 141.40ms | 94.63ms |
|
|
63
|
+
| Growing code fence | 43,209 | 1335.87ms | 29.05ms |
|
|
64
|
+
| Single long paragraph | 7,840 | 72.43ms | 74.32ms |
|
|
65
|
+
|
|
66
|
+
Changes include incremental line indexes, cached semantic facts for retained nodes, local finalization, an append path for growing code fences, and lazy code character maps. Complex growing paragraphs still need reparsing. Differences for a single long paragraph are small and subject to runtime variance; these results do not establish equal improvements for all content.
|
|
67
|
+
|
|
68
|
+
Run `pnpm bench:streaming` to measure the current core. For the alternating comparison, set `LIMR_BENCH_BASELINE` to a separate untouched checkout of `ebab991`, with the same dependencies available there. The script writes `benchmarks/results/streaming.json`; it does not recreate the historical hardware or timings.
|
|
69
|
+
|
|
70
|
+
Production Chromium 156, including synchronous React commits, forced layout, default typography and increment animations: a 34,860-character stream with default modules had **3.9ms P95**, 7.3ms maximum; a 43,211-character code stream had **2.6ms P95**, 12.4ms maximum. Three isolated font-ready pages per scenario, 256-character chunks; idle rAF time and async highlighting excluded. No old-browser-implementation comparison was made, and this is not physical-device input-to-display latency. Data: [streaming-browser.json](../../benchmarks/results/streaming-browser.json).
|
|
71
|
+
|
|
72
|
+
KP has a separate production benchmark using the complete scikit-learn Playground, including its TOC, sidebar and annotations. Three fresh pages per mode run three downward/upward sweeps each, then 32 viewport changes across 1100–1420px. The first sweep includes newly visible paragraphs; later sweeps reuse their typesetting. The table pools 1,080 scroll samples and 96 resize samples per mode, without CPU throttling.
|
|
73
|
+
|
|
74
|
+
| Measurement | Standard | Knuth–Plass |
|
|
75
|
+
| --- | ---: | ---: |
|
|
76
|
+
| Scroll median / P95 / maximum | 16.6 / 17.0 / 18.5ms | 16.6 / 19.8 / 46.5ms |
|
|
77
|
+
| Resize median / P95 / maximum | 11.2 / 24.1 / 31.9ms | 17.7 / 41.6 / 49.9ms |
|
|
78
|
+
| Scroll samples over 50ms | 0 / 1,080 | 0 / 1,080 |
|
|
79
|
+
| Resize samples over 50ms | 0 / 96 | 0 / 96 |
|
|
80
|
+
|
|
81
|
+
Source anchor lookup prunes unrelated source bindings and stops at the first visible character, avoiding a full KP DOM traversal for every sidebar item.
|
|
82
|
+
|
|
83
|
+
Scroll timings span `scrollTo` through the next rAF; resize timings include the viewport update, protocol overhead and the next rAF. KP still costs more during resizing, but this run did not reproduce the earlier long stalls. These are local Chromium measurements, not physical-device input latency or a guarantee for arbitrary plugins. Raw samples and exact boundaries: [layout.json](../../benchmarks/results/layout.json).
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pnpm build:playground
|
|
87
|
+
pnpm preview:playground --port 4174
|
|
88
|
+
# In another terminal, with no concurrent build or test:
|
|
89
|
+
pnpm exec tsx benchmarks/layout.ts
|
|
90
|
+
BENCH_URL=http://127.0.0.1:4174/LiMR pnpm exec tsx benchmarks/streaming-browser.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Preparation caches font measurements, hyphenation, legal breakpoints and prefix sums independently of line width. Rendering uses a word-spacing run per line instead of an element per space. Width changes patch glue in place when the breaks match, or reuse text containers when they change. A shared synchronous measurement Range avoids retaining thousands of live ranges across DOM mutations. Highlight geometry is cached in paragraph coordinates, adjacent KP paint fragments merge into lines, and paint writes do not force a layout per rectangle. Viewport-mask coordinates do not inherit into the article. Scrolling retains typeset nodes and does not rerun the paragraph solver.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Plugin packages
|
|
2
|
+
|
|
3
|
+
> Reading note: this page uses LiMR syntax and interactive examples. Some content cannot be fully displayed by ordinary Markdown viewers such as GitHub; visit the [GitHub Pages documentation](https://linloir.github.io/LiMR/#/docs/en/plugin-packages) for the best reading experience.
|
|
4
|
+
|
|
5
|
+
[Documentation](README.md) · [简体中文](../zh-CN/plugin-packages.md)
|
|
6
|
+
|
|
7
|
+
A plugin exports a factory returning RendererExtension. Consumers append it through plugins. One package can provide syntax, annotations, actions, and parameters, with a separate CSS export, without editing LiMR itself.
|
|
8
|
+
|
|
9
|
+
## Copyable starter
|
|
10
|
+
|
|
11
|
+
The complete [plugin-template](../../skills/limr-plugin-development/assets/plugin-template/README.md) includes TypeScript, declaration output, CSS, package.json, and tsup configuration. It demonstrates a reading-tip block, an example:note card, and a save-selection action.
|
|
12
|
+
|
|
13
|
+
After installing LiMR, run from your application:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
mkdir -p packages/limr-reading-tools
|
|
17
|
+
cp -R node_modules/@linloir/limr/skills/limr-plugin-development/assets/plugin-template/. packages/limr-reading-tools/
|
|
18
|
+
cd packages/limr-reading-tools
|
|
19
|
+
npm install
|
|
20
|
+
npm run typecheck
|
|
21
|
+
npm run build
|
|
22
|
+
npm pack
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
In a source checkout, use skills/limr-plugin-development/assets/plugin-template. Replace the @example name, extension/type/action IDs, and implement your data/UI. The starter's private: true prevents accidental publication under an example name and still permits local packing.
|
|
26
|
+
|
|
27
|
+
## Package contract
|
|
28
|
+
|
|
29
|
+
| Field | Requirement |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| type | module |
|
|
32
|
+
| exports["."] | import points to ESM; types points to .d.ts |
|
|
33
|
+
| exports["./styles.css"] | Explicit export when the plugin needs CSS |
|
|
34
|
+
| files | Built output and usage instructions |
|
|
35
|
+
| sideEffects | ["**/*.css"] to retain imported styles |
|
|
36
|
+
| peerDependencies | @linloir/limr and react; add react-dom when directly using it |
|
|
37
|
+
| Build external | LiMR, React, react/jsx-runtime, and any React DOM entry used |
|
|
38
|
+
|
|
39
|
+
Bundling another copy of LiMR or React breaks shared contexts/hooks. Externalize host dependencies and avoid internal deep imports. Ship parameter declaration merging with exported types.
|
|
40
|
+
|
|
41
|
+
## Import into an application
|
|
42
|
+
|
|
43
|
+
The @example package below is the starter, not a published npm package. Install the generated local archive; use the actual filename printed by npm pack:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm install ./packages/limr-reading-tools/example-limr-reading-tools-0.1.0.tgz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Reader } from '@linloir/limr';
|
|
51
|
+
import { readingTools } from '@example/limr-reading-tools';
|
|
52
|
+
import '@linloir/limr/styles.css';
|
|
53
|
+
import '@example/limr-reading-tools/styles.css';
|
|
54
|
+
|
|
55
|
+
const plugins = [readingTools({ label: 'Reading note' })];
|
|
56
|
+
|
|
57
|
+
export function Article({ source }: { source: string }) {
|
|
58
|
+
return <Reader source={source} plugins={plugins} />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use the real name and peer version requirements for a third-party package. Replace default modules through extensions + replaceExtension; do not register the same tag twice. Hosts still configure fonts, math CSS, persistence, and application services.
|
|
63
|
+
|
|
64
|
+
## Validate distribution
|
|
65
|
+
|
|
66
|
+
After type checking, install the actual tgz into an independent consumer. Check public imports, SSR, CSS resources, preserved defaults, and new interactions. Packages supporting React 18/19 should verify both. Syntax plugins should compare finalized streams with one-shot parsing. Interactive plugins should cover desktop sidebars, narrow overlays, keyboard behavior, cancellation, and both themes.
|
|
67
|
+
|
|
68
|
+
The repository also includes packable [reading-tools](../../examples/packages/reading-tools/index.js) and [paper-theme](../../examples/packages/paper-theme/index.css) examples. The [plugin development Skill](ai-skills.md) includes a portable template and references that do not require this repository.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Styles and fonts
|
|
2
|
+
|
|
3
|
+
> Reading note: this page uses LiMR syntax and interactive examples. Some content cannot be fully displayed by ordinary Markdown viewers such as GitHub; visit the [GitHub Pages documentation](https://linloir.github.io/LiMR/#/docs/en/styles) for the best reading experience.
|
|
4
|
+
|
|
5
|
+
[Documentation](README.md) · [简体中文](../zh-CN/styles.md)
|
|
6
|
+
|
|
7
|
+
## Base styles and themes
|
|
8
|
+
|
|
9
|
+
Default rules live in @layer limr, so ordinary host CSS can override them. Choose styles.css (with Maple) or styles/base.css (without loading Maple), and add math.css for formulas.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Reader } from '@linloir/limr';
|
|
13
|
+
import '@linloir/limr/styles/base.css';
|
|
14
|
+
import './paper.css';
|
|
15
|
+
|
|
16
|
+
export function Paper({ source }: { source: string }) {
|
|
17
|
+
return <Reader source={source} appearance="paper" theme="auto" />;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Example paper.css:
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
[data-limr-appearance='paper'] {
|
|
25
|
+
--limr-font-text: Georgia, 'Noto Serif SC', serif;
|
|
26
|
+
--limr-content-width: 820px;
|
|
27
|
+
--limr-accent: #8b6548;
|
|
28
|
+
}
|
|
29
|
+
[data-limr-appearance='paper'][data-limr-theme='dark'] {
|
|
30
|
+
--limr-accent: #d1ab84;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
appearance propagates to portalled controls. Do not rely only on .limr-reader ancestry. theme: auto selects built-in colors with the system preference; custom dark overrides for auto also need a prefers-color-scheme media rule. See the standalone [paper-theme](../../examples/packages/paper-theme/index.css) example.
|
|
35
|
+
|
|
36
|
+
## Common variables
|
|
37
|
+
|
|
38
|
+
| Variable | Purpose |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| --limr-font-text / --limr-font-code | Body / code fonts |
|
|
41
|
+
| --limr-font-size / --limr-line-height | Base typography |
|
|
42
|
+
| --limr-font-features | Defaults to normal, preserving full-width Chinese punctuation; opt into 'palt' 1 for proportional spacing |
|
|
43
|
+
| --limr-punctuation-spacing | Defaults to space-all, preserving full-width spacing between CJK punctuation |
|
|
44
|
+
| --limr-emphasis-spacing / --limr-list-item-gap | Trailing emphasis / list spacing |
|
|
45
|
+
| --limr-text / --limr-heading / --limr-secondary / --limr-muted | Text hierarchy |
|
|
46
|
+
| --limr-background / --limr-surface / --limr-border | Backgrounds and borders |
|
|
47
|
+
| --limr-link / --limr-accent / --limr-selection | Links, active state, selection |
|
|
48
|
+
| --limr-content-width / --limr-reader-width | Body / overall width; default 900px / 1800px |
|
|
49
|
+
| --limr-toc-width / --limr-sidebar-width / --limr-column-gap | Side column widths and gaps |
|
|
50
|
+
| --limr-sticky-top | Sticky offset |
|
|
51
|
+
| --limr-reader-bottom-space / --limr-reader-min-height | Short-document height and bottom space |
|
|
52
|
+
| --limr-sidebar-padding-inline / --limr-sidebar-reference-gap | Shared padding (10px) and reference separation (1px) |
|
|
53
|
+
| --limr-overlay-duration / --limr-overlay-morph-duration | Enter/exit (560ms), preview/expanded morph (480ms) |
|
|
54
|
+
| --limr-overlay-z / --limr-toolbar-z | Floating control stacking |
|
|
55
|
+
| --limr-overflow-fade-size | Overflow fade, default 24px; 0px disables |
|
|
56
|
+
| --limr-toolbar-panel-max-height | Maximum toolbar panel height |
|
|
57
|
+
| --limr-tool-icon-size / --limr-mark-size | Toolbar icons / inline markers |
|
|
58
|
+
| --limr-mermaid-max-height | Diagram viewport maximum height; default min(70vh, 36rem) |
|
|
59
|
+
| --limr-mermaid-scrollbar | Thin diagram scrollbar color; default #87837866 |
|
|
60
|
+
|
|
61
|
+
--limr-toolbar-available-height, --limr-toolbar-panel-height, and --limr-toc-available-height are runtime measurements, not theme inputs. AnnotationType.sidebar usually supplies local card colors; see [extensions](extensions.md).
|
|
62
|
+
|
|
63
|
+
## Fonts and package size
|
|
64
|
+
|
|
65
|
+
Local WOFF2 subsets of Maple Mono NF CN v7.9 ship in the package under the derivative name LiMR Maple Mono NF CN, with the OFL license. unicode-range and font-display: swap limit runtime requests, but the complete font assets still occupy about 31MB. base.css avoids browser loading, not npm installation size.
|
|
66
|
+
|
|
67
|
+
Body text does not automatically load Chinese fonts. Use system fallbacks, self-hosted fonts, or a host-chosen service. The Playground's Noto Serif SC belongs to the demo. No font should be assumed to cover every script.
|
|
68
|
+
|
|
69
|
+
Default font/math styles do not require a third-party CDN. Hosts can apply long-lived immutable caching to hashed font URLs; the library cannot set server HTTP headers. Remote image/video/plugin requests depend on content and host configuration.
|
|
70
|
+
|
|
71
|
+
## Layout and interaction conventions
|
|
72
|
+
|
|
73
|
+
The reading region determines synchronized sidebar height; do not size the page to all cards combined. Modules own content while the shared layout owns references, summaries, and card shells. Avoid duplicate padding or empty-paragraph compensation. Expanded cards animate from preview geometry; reduced-motion preferences disable motion.
|
|
74
|
+
|
|
75
|
+
Dense footer references borrow spare space above them at rest. Hovered or active items take alignment priority, allowing neighboring items to clip at the edges. Marker hover can compress the optional summary to leave room for the reference and heading. Expanded content is bounded by the visible reading region and scrolls within its card. Packed sidebar items still align the prose on hover. Deactivation smoothly restores the resting layout using current card heights, `--limr-motion-slow`, and `--limr-ease`, preserving gaps and page height.
|
|
76
|
+
|
|
77
|
+
Selection retains native browser ranges and keyboard behavior. Paint layers calculate visible fragments only. Code allows partial highlights, math is atomic, and image highlights use borders. A single atomic block anchors its toolbar below the block. Printing hides paint layers while keeping document content. Mobile automation does not certify native iOS/Android handles or physical-device behavior.
|
|
78
|
+
|
|
79
|
+
Selection-toolbar copy feedback crossfades with a small scale change. Its label width grows and shrinks to the active face with nonlinear easing through `--limr-ease`; reduced-motion preferences remove these transitions.
|
|
80
|
+
|
|
81
|
+
Mermaid diagrams support drag panning, native touch/trackpad scrolling, arrow keys, Page Up/Down, Home/End, and Shift+wheel for horizontal movement. Click or Enter selects the whole block; dragging never selects its internal labels. Zoom scrolls inside the bounded viewport. The shared glass toolbar appears on hover/focus (always on touch screens); button feedback fades its background without scaling. Source mode keeps ordinary top padding. Themes and plugins can override the diagram height and scrollbar variables on the Reader or block.
|
|
82
|
+
|
|
83
|
+
## CSS-only themes
|
|
84
|
+
|
|
85
|
+
Export CSS, include it in files, and mark sideEffects as ["**/*.css"]. A theme does not need React or parser runtime dependencies. Scope rules to an appearance name and check light, dark, auto, portals, and two differently themed readers on the same page.
|
|
86
|
+
|
|
87
|
+
Expanded toolbar panels use the full available toolbar width. The action row and available viewport determine its width; expanded content does not contribute intrinsic width or enlarge the toolbar. Highlight options use intrinsic text widths and wrap as whole items, aligned to the start. Multiline underlines need an inner inline span rather than a direct flex child.
|
|
88
|
+
|
|
89
|
+
## Paragraph layout and streaming
|
|
90
|
+
|
|
91
|
+
`typography="standard"` is the default. `justify` uses browser justification. `knuth-plass` uses TeX's box/glue/penalty dynamic program: four fitness classes, integer badness, squared penalties, consecutive/final hyphen demerits, pretolerance, tolerance and emergency passes. It optimizes the whole paragraph without a beam or paragraph-length cutoff. Default English hyphenation uses Knuth–Liang patterns; Unicode break opportunities also support Chinese punctuation. Browser font shaping remains authoritative, so this does not promise pixel-identical TeX PDF output.
|
|
92
|
+
|
|
93
|
+
`knuthPlass` accepts `pretolerance`, `tolerance`, `linePenalty`, `hyphenPenalty`, `adjacentDemerits`, `doubleHyphenDemerits`, `finalHyphenDemerits`, `emergencyStretch` (CSS pixels), `looseness`, and `hyphenate` (`false` or a function returning the word's parts). Core defaults follow TeX's usual paragraph parameters, with 16px of emergency stretch. The browser adapter instead derives 3em from the font size for break scoring only; an explicit `knuthPlass.emergencyStretch` overrides it. `pretty` and `normal` remain optional legacy CSS modes.
|
|
94
|
+
|
|
95
|
+
KP loads lazily and typesets stable paragraphs near the viewport. It retains their DOM when they leave the viewport, reuses measured items and prefix sums for width changes, and preserves native selections before updating text. During shrink, offscreen cached lines clip horizontal overflow until their queued exact layout is ready; an actively selected paragraph temporarily wraps while keeping its native range. Active streaming tails use normal wrapping until stable. Layout never changes original source text, annotations or copy output. See [performance](performance.md) for measured scroll/resize comparisons and [TeX's source](https://github.com/TeX-Live/texlive-source/blob/trunk/texk/web2c/tex.web) for the underlying paragraph model. The separation of preparation and layout also follows the incremental approach used by [Typst](https://github.com/typst/typst/tree/main/crates/typst-layout/src/inline).
|
|
96
|
+
|
|
97
|
+
Default loading rings use a fixed 32×32 view box, 22px display size, a thick track and an animated arc. Rotation and arc growth/shrink use nonlinear easing around one fixed center; color follows `--limr-secondary`. Streaming text and blocks fade from transparent to opaque, without a background tint or a second paragraph-completion entrance. Plugins can replace the shared effect through `streaming.effect`, and each Reader can override it through `streamEffect`.
|
|
98
|
+
|
|
99
|
+
Source-authored highlight blue is controlled by `--limr-inline-highlight`, or replace the module using `inlineHighlights({ color })`. Localize the read-only toolbar hint with `locale.inlineHighlightHint`.
|