@fraczak/k 7.1.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/DOCS/CODECS.md +37 -2
- package/DOCS/CODEC_ARCHITECTURE.md +12 -5
- package/DOCS/REPL.md +11 -3
- package/DOCS/WEB_REPL_REQUIREMENTS.md +1 -1
- package/README.md +2 -1
- package/Value.mjs +28 -3
- package/backend-api.mjs +5 -1
- package/browser/tree-view.mjs +47 -8
- package/browser/vfs-data.mjs +6 -5
- package/codecs/README.md +1 -0
- package/codecs/djson.mjs +270 -0
- package/codecs/int.mjs +2 -1
- package/codecs/json.mjs +7 -2
- package/codecs/k-parse.mjs +7 -1
- package/codecs/k-print.mjs +4 -0
- package/codecs/runtime/codec-sdk.mjs +16 -2
- package/codecs/runtime/pattern-json.mjs +80 -1
- package/codecs/runtime/prefix-codec.mjs +629 -67
- package/codecs/runtime/show-value.mjs +93 -51
- package/codecs/runtime/unicode-string.mjs +29 -6
- package/codecs/show.mjs +4 -0
- package/codecs/utf16.mjs +2 -1
- package/package.json +2 -1
- package/repl.mjs +57 -6
- package/scripts/build-repl-html.mjs +8 -1
- package/tests/test-codecs.mjs +168 -0
- package/tests/test-repl-html.mjs +1 -0
- package/tests/test-repl.mjs +33 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
6
|
This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [7.2.0] — 2026-10-17 — Dynamic JSON Codec (djson) & Lazy Wire Decoding
|
|
9
|
+
|
|
10
|
+
### Features & Performance Improvements
|
|
11
|
+
|
|
12
|
+
- **Universal Dynamic JSON Codec (`codecs/djson.mjs`, `k-djson`)**:
|
|
13
|
+
- Added a standalone dynamic JSON codec (`djson.mjs` / `k-djson`)
|
|
14
|
+
based on a single 29-node recursive pattern `JSON = < number,
|
|
15
|
+
string, bool, null, list, object >`, where arrays are encoded as
|
|
16
|
+
arbitrary-length `cons`/`nil` lists and objects as `cons`/`nil`
|
|
17
|
+
lists of `{ key, val }` pairs.
|
|
18
|
+
- Kept the existing schema-specializing `json.mjs` (`k-json`) as an
|
|
19
|
+
alternative record-oriented JSON codec.
|
|
20
|
+
- Integrated `djson.mjs` into the CLI, REPL, and Web REPL (`repl.html`
|
|
21
|
+
VFS).
|
|
22
|
+
|
|
23
|
+
- **Lazy Prefix-Free Wire Decoding & Fast Bitstream I/O**:
|
|
24
|
+
- Implemented lazy wire decoding (`LazyGeneralProduct` and
|
|
25
|
+
`LazyGeneralVariant`) with precomputed fixed-width bit sizes and
|
|
26
|
+
static field offsets on pattern graphs.
|
|
27
|
+
- Optimized bitstream reading/writing (`BitReader` / `BitWriter`) with
|
|
28
|
+
word-level operations, pre-encoded closed pattern bytes, and direct
|
|
29
|
+
bitstream-to-codepoint UTF-8 string decoding without intermediate
|
|
30
|
+
AST allocations.
|
|
31
|
+
|
|
32
|
+
- **Stack-Safe & Bounded Value Rendering**:
|
|
33
|
+
- Converted `valueToK`, `valueWithEnvelopeToK`, and wire serialization
|
|
34
|
+
to iterative stack-safe algorithms with early character truncation
|
|
35
|
+
(`maxChars`).
|
|
36
|
+
- Added `:limit [num|none]` command and `--limit=<num>` CLI flag in
|
|
37
|
+
`repl.mjs` (default: `4096` chars) and bounded DOM tree previews in
|
|
38
|
+
`repl.html`.
|
|
39
|
+
- Handled broken pipe (`EPIPE`) cleanly across codec CLI entrypoints.
|
|
40
|
+
|
|
8
41
|
## [7.1.0] — 2026-10-16 — Polymorphic K IR (PKIR) & CLI Standardization
|
|
9
42
|
|
|
10
43
|
### Features & Architecture Improvements
|
package/DOCS/CODECS.md
CHANGED
|
@@ -141,6 +141,40 @@ printf 'yes\n' | ./codecs/yesno.mjs --parse | ./codecs/yesno.mjs --print
|
|
|
141
141
|
# Output: yes
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
## Built-in Codecs
|
|
145
|
+
|
|
146
|
+
The `codecs/` directory provides the following standard codecs:
|
|
147
|
+
|
|
148
|
+
- **`int.mjs` (`k-int`)**: Decimal integers and bracketed integer lists
|
|
149
|
+
(e.g. `42`, `-15`, `[1, 2, 3]`) mapped to signed binary bit trees and
|
|
150
|
+
`cons`/`nil` lists.
|
|
151
|
+
- **`ieee.mjs` (`k-ieee`)**: IEEE 754 64-bit floating-point numbers
|
|
152
|
+
(`sign`, `exponent`, `fraction`).
|
|
153
|
+
- **`utf8.mjs` (`k-utf8`) & `utf16.mjs` (`k-utf16`)**: Unicode strings
|
|
154
|
+
encoded as `cons`/`nil` code-point lists (`str` in `core.k`).
|
|
155
|
+
- **`unit.mjs` (`k-unit`)**: The unit value `{}`.
|
|
156
|
+
- **`json.mjs` (`k-json`) — Schema-Specializing JSON**:
|
|
157
|
+
Derives a document-specific pattern for each JSON value:
|
|
158
|
+
- JSON objects map directly to products `{ field: val, ... }`.
|
|
159
|
+
- JSON arrays map to indexed products `{ "0": a0, "1": a1, ... }`.
|
|
160
|
+
- Ideal for record-oriented projection (e.g. `.user .name`).
|
|
161
|
+
- **`djson.mjs` (`k-djson`) — Universal Dynamic JSON**:
|
|
162
|
+
Maps every JSON document to a single universal recursive pattern
|
|
163
|
+
`JSON`:
|
|
164
|
+
```k
|
|
165
|
+
?<
|
|
166
|
+
float64 number,
|
|
167
|
+
str string,
|
|
168
|
+
< {} true, {} false > bool,
|
|
169
|
+
{} null,
|
|
170
|
+
< {} nil, { JSON car, LIST cdr } cons >=LIST list,
|
|
171
|
+
< {} nil, { { str key, JSON val } car, OBJ cdr } cons >=OBJ object
|
|
172
|
+
>=JSON
|
|
173
|
+
```
|
|
174
|
+
Because every JSON value shares the exact same static 29-node pattern,
|
|
175
|
+
a `k` relation can be compiled once against `JSON` and process
|
|
176
|
+
arbitrary JSON documents of any shape or array/object length.
|
|
177
|
+
|
|
144
178
|
## Testing a Codec
|
|
145
179
|
|
|
146
180
|
For CLI codecs, test both directions in a pipeline:
|
|
@@ -174,8 +208,9 @@ npm test
|
|
|
174
208
|
## Web REPL (VFS) Bundling
|
|
175
209
|
|
|
176
210
|
When `repl.html` is generated (`npm run build:repl-html`):
|
|
177
|
-
- Codecs (`int.mjs`, `json.mjs`, `
|
|
178
|
-
bundled into 100% self-contained ES modules with zero
|
|
211
|
+
- Codecs (`int.mjs`, `json.mjs`, `djson.mjs`, `utf8.mjs`, `unit.mjs`,
|
|
212
|
+
`ieee.mjs`) are bundled into 100% self-contained ES modules with zero
|
|
213
|
+
imports.
|
|
179
214
|
- `runCodecCLI` is shimmed as a no-op, and `Value` helpers are directly
|
|
180
215
|
inlined.
|
|
181
216
|
- In the browser, the VFS serves these self-contained modules directly to
|
|
@@ -192,11 +192,16 @@ runCodecCLI(import.meta.url, { parse, print, doc });
|
|
|
192
192
|
pathToFileURL(process.argv[1]).href`). If imported as a module or
|
|
193
193
|
running in a browser, it is a no-op.
|
|
194
194
|
2. **Flag Parsing**: Handles `--parse`, `--print`, and `-h` / `--help`.
|
|
195
|
-
3. **Stream Piping**:
|
|
195
|
+
3. **Stream Piping & Lazy Wire Decoding**:
|
|
196
196
|
- For `--parse`: reads `stdin` to string, calls `parse(text)`, encodes
|
|
197
197
|
enveloped value `(P, v)` to binary wire format on `stdout`.
|
|
198
|
-
- For `--print`: decodes wire format from `stdin` into
|
|
199
|
-
`
|
|
198
|
+
- For `--print`: lazily decodes the wire format from `stdin` into
|
|
199
|
+
bitstream-backed `Value` proxies (`LazyGeneralProduct`,
|
|
200
|
+
`LazyGeneralVariant`) with precomputed fixed-width bit sizes and
|
|
201
|
+
static field offsets, calls `print(value)`, and writes formatted text
|
|
202
|
+
to `stdout`.
|
|
203
|
+
- Cleanly exits on `EPIPE` when piped into downstream consumers like
|
|
204
|
+
`head`.
|
|
200
205
|
|
|
201
206
|
---
|
|
202
207
|
|
|
@@ -207,7 +212,8 @@ To make `codecs/` intuitive and self-contained:
|
|
|
207
212
|
```text
|
|
208
213
|
codecs/
|
|
209
214
|
├── int.mjs # Integer & list codec
|
|
210
|
-
├── json.mjs # JSON codec
|
|
215
|
+
├── json.mjs # JSON codec (schema-specializing)
|
|
216
|
+
├── djson.mjs # Dynamic JSON codec (singleton pattern)
|
|
211
217
|
├── utf8.mjs # UTF-8 string codec
|
|
212
218
|
├── utf16.mjs # UTF-16 string codec
|
|
213
219
|
├── ieee.mjs # IEEE 754 float codec
|
|
@@ -236,7 +242,8 @@ In the browser REPL (`repl.html`):
|
|
|
236
242
|
imports**.
|
|
237
243
|
- **No Runtime Files in VFS**: `codecs/runtime/*` is not included in the
|
|
238
244
|
browser VFS. The VFS only contains verified, self-contained user-facing
|
|
239
|
-
files (`int.mjs`, `json.mjs`, `
|
|
245
|
+
files (`int.mjs`, `json.mjs`, `djson.mjs`, `utf8.mjs`, `unit.mjs`,
|
|
246
|
+
`ieee.mjs`).
|
|
240
247
|
- **Native Browser Loading**: In `repl-codecs.mjs`, the browser reads the
|
|
241
248
|
codec source from VFS, wraps it in a Blob, and invokes native `await
|
|
242
249
|
import(blobUrl)`. No regex manipulation, import stripping, or global
|
package/DOCS/REPL.md
CHANGED
|
@@ -33,10 +33,11 @@ is complete.
|
|
|
33
33
|
| Command | Meaning |
|
|
34
34
|
| --- | --- |
|
|
35
35
|
| `:help` | Show command summary |
|
|
36
|
-
| `:engine [wasm
|
|
37
|
-
| `:wasm` / `:js` | Shortcut to switch engine |
|
|
36
|
+
| `:engine [wasm\|pkir\|js]` | Display or switch evaluation engine (`wasm`, `pkir`, or `js`) |
|
|
37
|
+
| `:wasm` / `:pkir` / `:js` | Shortcut to switch engine |
|
|
38
38
|
| `:rel name` | Show relation definition |
|
|
39
39
|
| `:rels` | List relation aliases |
|
|
40
|
+
| `:limit [num\|none]` | Set maximum characters for value output (default: 4096) |
|
|
40
41
|
| `:codec load file` | Load a codec from an ES module file (e.g. `:codec load codecs/int.mjs`) |
|
|
41
42
|
| `:codec unload name` | Unload a registered codec |
|
|
42
43
|
| `:codec list` | List loaded REPL codecs (alias: `:codecs`) |
|
|
@@ -231,11 +232,13 @@ The REPL supports two evaluation engines:
|
|
|
231
232
|
|
|
232
233
|
### CLI Options
|
|
233
234
|
|
|
234
|
-
When starting `k-repl`, choose the engine with:
|
|
235
|
+
When starting `k-repl`, choose the engine or output limit with:
|
|
235
236
|
|
|
236
237
|
```bash
|
|
237
238
|
k-repl --engine=js # or k-repl --js
|
|
239
|
+
k-repl --engine=pkir # or k-repl --pkir
|
|
238
240
|
k-repl --engine=wasm # or k-repl --wasm (default)
|
|
241
|
+
k-repl --limit=8192 # set max characters for rendered output
|
|
239
242
|
```
|
|
240
243
|
|
|
241
244
|
## Timing and Profiling
|
|
@@ -297,6 +300,11 @@ Evaluated values print in k syntax together with the inferred envelope:
|
|
|
297
300
|
{}|0|+1 ?<<{} 0, ...> +1, ...>
|
|
298
301
|
```
|
|
299
302
|
|
|
303
|
+
Large values are rendered iteratively and truncated at `maxChars`
|
|
304
|
+
(default: `4096` characters) so deeply nested or large values print
|
|
305
|
+
instantly without exhausting stack or terminal buffers. Use
|
|
306
|
+
`:limit <num>` or `:limit none` to adjust or disable the limit.
|
|
307
|
+
|
|
300
308
|
`undefined` prints as:
|
|
301
309
|
|
|
302
310
|
```text
|
|
@@ -113,7 +113,7 @@ The bundled VFS and UI examples must include only verified useful files:
|
|
|
113
113
|
#### 1. Curated Files Explorer
|
|
114
114
|
In the Web REPL "Files" (VFS) explorer, the visible files are strictly curated to verified examples and codecs:
|
|
115
115
|
- **k-codes**: `core.k`, `arithmetics.k`, `ieee.k`
|
|
116
|
-
- **codecs**: `json.mjs`, `utf8.mjs`, `int.mjs`, `unit.mjs`, `ieee.mjs`
|
|
116
|
+
- **codecs**: `json.mjs`, `djson.mjs`, `utf8.mjs`, `int.mjs`, `unit.mjs`, `ieee.mjs`
|
|
117
117
|
- User-created or uploaded files remain visible.
|
|
118
118
|
- Internal runtime artifacts (`backends/wasm/runtime.wat`) and redundant directory prefixes (`Examples/`, `codecs/`) are kept accessible internally but hidden from the default file listing.
|
|
119
119
|
- Clicking "Load" on a `.mjs` file automatically executes `:codec load <filename>`.
|
package/README.md
CHANGED
|
@@ -223,7 +223,8 @@ For a detailed guide to all four formats, commands, and workflows, see
|
|
|
223
223
|
|
|
224
224
|
| Command | Purpose |
|
|
225
225
|
| --- | --- |
|
|
226
|
-
| `k-json` | Convert JSON to/from the binary stream |
|
|
226
|
+
| `k-json` | Convert JSON to/from the binary stream (schema-specializing) |
|
|
227
|
+
| `k-djson` | Convert JSON to/from a universal dynamic JSON singleton pattern |
|
|
227
228
|
| `k-int` | Convert decimal integers to/from the binary stream |
|
|
228
229
|
| `k-ieee` | Convert float literals to/from the binary stream |
|
|
229
230
|
| `k-unit` | Produce or validate the unit value |
|
package/Value.mjs
CHANGED
|
@@ -193,15 +193,40 @@ class Value {
|
|
|
193
193
|
|
|
194
194
|
toJSON() {
|
|
195
195
|
if (isVariant(this)) {
|
|
196
|
-
if (
|
|
196
|
+
if (
|
|
197
|
+
isProduct(this.value) &&
|
|
198
|
+
Object.keys(this.value.product).length === 0
|
|
199
|
+
) {
|
|
197
200
|
return this.tag;
|
|
198
201
|
}
|
|
199
|
-
|
|
202
|
+
const tags = [];
|
|
203
|
+
let curr = this;
|
|
204
|
+
while (isVariant(curr)) {
|
|
205
|
+
if (
|
|
206
|
+
isProduct(curr.value) &&
|
|
207
|
+
Object.keys(curr.value.product).length === 0
|
|
208
|
+
) {
|
|
209
|
+
let res = curr.tag;
|
|
210
|
+
for (let i = tags.length - 1; i >= 0; i--) {
|
|
211
|
+
res = { [tags[i]]: res };
|
|
212
|
+
}
|
|
213
|
+
return res;
|
|
214
|
+
}
|
|
215
|
+
tags.push(curr.tag);
|
|
216
|
+
curr = curr.value;
|
|
217
|
+
}
|
|
218
|
+
let res = curr
|
|
219
|
+
? (typeof curr.toJSON === "function" ? curr.toJSON() : curr)
|
|
220
|
+
: null;
|
|
221
|
+
for (let i = tags.length - 1; i >= 0; i--) {
|
|
222
|
+
res = { [tags[i]]: res };
|
|
223
|
+
}
|
|
224
|
+
return res;
|
|
200
225
|
}
|
|
201
226
|
if (!isProduct(this)) return { type: this.type };
|
|
202
227
|
let vector = toVector(this.product);
|
|
203
228
|
if (vector.length > 0) return vector;
|
|
204
|
-
return this.product;
|
|
229
|
+
return this.product;
|
|
205
230
|
}
|
|
206
231
|
}
|
|
207
232
|
export { Value, fromObject, cloneSubpattern, edgeSubpattern, composePattern, mergePatterns, withPattern, isProduct, isVariant, isValue };
|
package/backend-api.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { annotate } from "./index.mjs";
|
|
2
2
|
export { KVM_FORMAT, KVM_VERSION, lowerKIRRelationToKVM, lowerKIRToKVM, lowerToKVM, executeKVM, objectToKVMArtifact, specializeKVM } from "./kvm.mjs";
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
decodeWire,
|
|
5
|
+
decodeWireLazy,
|
|
6
|
+
encodeToWire
|
|
7
|
+
} from "./codecs/runtime/prefix-codec.mjs";
|
|
4
8
|
export { exportPatternGraph, NODE_KIND } from "./codecs/runtime/codec.mjs";
|
|
5
9
|
export { patternToPropertyList, propertyListToPattern } from "./codecs/runtime/pattern-json.mjs";
|
|
6
10
|
export { Value, fromObject, isProduct, isVariant, isValue } from "./Value.mjs";
|
package/browser/tree-view.mjs
CHANGED
|
@@ -101,7 +101,9 @@ export function renderValueNode(val, currentDepth = 1, depthLimit = 3) {
|
|
|
101
101
|
const populateChildren = () => {
|
|
102
102
|
if (populated) return;
|
|
103
103
|
populated = true;
|
|
104
|
-
|
|
104
|
+
const maxFields = 100;
|
|
105
|
+
const displayKeys = keys.slice(0, maxFields);
|
|
106
|
+
for (const k of displayKeys) {
|
|
105
107
|
const row = document.createElement("div");
|
|
106
108
|
row.className = "k-tree-row";
|
|
107
109
|
|
|
@@ -113,12 +115,25 @@ export function renderValueNode(val, currentDepth = 1, depthLimit = 3) {
|
|
|
113
115
|
const childContainer = document.createElement("div");
|
|
114
116
|
childContainer.className = "k-tree-child-val";
|
|
115
117
|
childContainer.appendChild(
|
|
116
|
-
renderValueNode(
|
|
118
|
+
renderValueNode(
|
|
119
|
+
val.product[k],
|
|
120
|
+
isOpen ? currentDepth + 1 : 1,
|
|
121
|
+
depthLimit
|
|
122
|
+
)
|
|
117
123
|
);
|
|
118
124
|
row.appendChild(childContainer);
|
|
119
125
|
|
|
120
126
|
childrenContainer.appendChild(row);
|
|
121
127
|
}
|
|
128
|
+
if (keys.length > maxFields) {
|
|
129
|
+
const moreRow = document.createElement("div");
|
|
130
|
+
moreRow.className = "k-tree-row k-tree-more";
|
|
131
|
+
moreRow.style.color = "var(--color-muted)";
|
|
132
|
+
moreRow.style.paddingLeft = "1.2rem";
|
|
133
|
+
moreRow.textContent =
|
|
134
|
+
`... and ${keys.length - maxFields} more fields`;
|
|
135
|
+
childrenContainer.appendChild(moreRow);
|
|
136
|
+
}
|
|
122
137
|
};
|
|
123
138
|
|
|
124
139
|
if (isOpen) {
|
|
@@ -169,13 +184,15 @@ export function renderValueNode(val, currentDepth = 1, depthLimit = 3) {
|
|
|
169
184
|
if (isVariant(val.value)) {
|
|
170
185
|
let chainCount = 0;
|
|
171
186
|
let curr = val.value;
|
|
172
|
-
while (isVariant(curr)) {
|
|
187
|
+
while (isVariant(curr) && chainCount < 20) {
|
|
173
188
|
chainCount++;
|
|
174
189
|
curr = curr.value;
|
|
175
190
|
}
|
|
176
191
|
const badge = document.createElement("span");
|
|
177
192
|
badge.className = "k-tree-badge";
|
|
178
|
-
badge.textContent =
|
|
193
|
+
badge.textContent = isVariant(curr)
|
|
194
|
+
? "+20+ tags"
|
|
195
|
+
: `+${chainCount} tags`;
|
|
179
196
|
summary.appendChild(badge);
|
|
180
197
|
}
|
|
181
198
|
|
|
@@ -486,7 +503,15 @@ export function createOutputEntryElement({
|
|
|
486
503
|
|
|
487
504
|
const valSummary = document.createElement("summary");
|
|
488
505
|
valSummary.className = "k-tree-section-summary";
|
|
489
|
-
|
|
506
|
+
const rawValPreview = valueToK(value, 120);
|
|
507
|
+
const valPreview = rawValPreview.length > 120
|
|
508
|
+
? `${rawValPreview.slice(0, 120)}...`
|
|
509
|
+
: rawValPreview;
|
|
510
|
+
const valEscaped = escapeHtml(valPreview);
|
|
511
|
+
valSummary.innerHTML =
|
|
512
|
+
`<span class="k-tree-arrow">▶</span> ` +
|
|
513
|
+
`<span class="k-section-title">🌲 Value</span> ` +
|
|
514
|
+
`<span class="k-section-preview">${valEscaped}</span>`;
|
|
490
515
|
valSection.appendChild(valSummary);
|
|
491
516
|
|
|
492
517
|
const valBody = document.createElement("div");
|
|
@@ -504,8 +529,15 @@ export function createOutputEntryElement({
|
|
|
504
529
|
|
|
505
530
|
const patSummary = document.createElement("summary");
|
|
506
531
|
patSummary.className = "k-tree-section-summary";
|
|
507
|
-
const
|
|
508
|
-
|
|
532
|
+
const rawPatPreview = propertyListToFilter(value.pattern, "X", 120);
|
|
533
|
+
const patPreview = rawPatPreview.length > 120
|
|
534
|
+
? `${rawPatPreview.slice(0, 120)}...`
|
|
535
|
+
: rawPatPreview;
|
|
536
|
+
const patEscaped = escapeHtml(patPreview);
|
|
537
|
+
patSummary.innerHTML =
|
|
538
|
+
`<span class="k-tree-arrow">▶</span> ` +
|
|
539
|
+
`<span class="k-section-title">🌿 Pattern</span> ` +
|
|
540
|
+
`<span class="k-section-preview">?${patEscaped}</span>`;
|
|
509
541
|
patSection.appendChild(patSummary);
|
|
510
542
|
|
|
511
543
|
patBody = document.createElement("div");
|
|
@@ -545,7 +577,14 @@ export function createOutputEntryElement({
|
|
|
545
577
|
|
|
546
578
|
const rawPre = document.createElement("pre");
|
|
547
579
|
rawPre.className = "k-raw-pre";
|
|
548
|
-
|
|
580
|
+
const MAX_RAW_PRE_CHARS = 100000;
|
|
581
|
+
if (rawText.length > MAX_RAW_PRE_CHARS) {
|
|
582
|
+
rawPre.textContent =
|
|
583
|
+
rawText.slice(0, MAX_RAW_PRE_CHARS) +
|
|
584
|
+
`\n\n... [truncated, ${rawText.length - MAX_RAW_PRE_CHARS} more characters]`;
|
|
585
|
+
} else {
|
|
586
|
+
rawPre.textContent = rawText;
|
|
587
|
+
}
|
|
549
588
|
rawWrapper.appendChild(rawPre);
|
|
550
589
|
|
|
551
590
|
container.appendChild(rawWrapper);
|