feat(linkedin-studio): make long-form review language configurable [skip-docs]
Wave 3 / Step 9 of the remediation plan (Phase 1 — usable by a non-author).
The long-form review layer shipped Norwegian-locked: language-reviewer graded
unconditional Norwegian, voice-scrubber's gold standard was 'approved Norwegian
editions', and the editorial/content craft gates pointed at a 'skrivekontrakt §C2'
that does not ship. A non-Norwegian adopter would get English prose graded against
Norwegian idiom and a gate that depends on an unshipped contract.
- config/edition-state.template.json: add additive 'language' field (top-level,
default 'en') + a _doc entry. Threads into the language-dependent agents.
- agents/language-reviewer.md: new 'Language parameter' section — Norwegian-specific
checks (anglicism->Norwegian idiom, kanselli-stil) apply only when language=='no';
any other value grades that language's equivalents and never flags idiomatic
English as an anglicism. Default 'en'.
- agents/voice-scrubber.md: gold standard reframed to 'approved editions in the
configured language'; the Norwegian-chronicle calibration is the language=='no'
instantiation.
- agents/editorial-reviewer.md + agents/content-reviewer.md: the in-tree checklist
is now the operative, self-contained source of truth; Maskinrommet §C2 is an
optional upstream contract that does NOT ship (available only on the author's
runs). The gates work for an adopter without it.
- commands/newsletter.md: thread 'language' through the Step 6.5 cold-inputs and the
per-reviewer call inputs; the writing contract is now 'if it ships'.
Norwegian remains fully working when language: no (the author's case).
fact-reviewer.md was in the plan's file list but needed no change on inspection:
its F1-F4 checks (claims/quotes/numbers/sources) are language-agnostic; its
'Norwegian' mentions are boundary notes vs language-reviewer, which stay correct.
[skip-docs]: three-doc + version reconciliation is Step 21 (pre-review-gate); these
intermediate Wave commits are not pushed before the /trekreview gate.
Verify: edition-state JSON parses + has top-level language 'en'; language-reviewer
has 'language ==' references and no unconditional-Norwegian assertion; editorial
§C2 reframed to in-tree fallback ('operative source', 'does not ship'); agent
fixtures 35/35 pass; structural lint exit 0 (61 passed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
305b99c0e4
commit
8d2968a482
6 changed files with 68 additions and 29 deletions
|
|
@ -58,8 +58,10 @@ sentence is not. If you ever hand back edited prose, you have failed the role.
|
||||||
## Context isolation — you are a COLD reader (cardinal)
|
## Context isolation — you are a COLD reader (cardinal)
|
||||||
|
|
||||||
> You are an **adversarial, independent** reviewer, run in a **cold context**.
|
> You are an **adversarial, independent** reviewer, run in a **cold context**.
|
||||||
> Your entire input is: this prompt, the path to a **frozen draft**, and the
|
> Your entire input is: this prompt (with its self-contained C1–C5 checklist) and
|
||||||
> writing contract. You have **no** access to — and must **refuse to act on** —
|
> the path to a **frozen draft** — no external writing contract ships or is
|
||||||
|
> required to run this gate. You have **no** access to — and must **refuse to act
|
||||||
|
> on** —
|
||||||
> any of:
|
> any of:
|
||||||
> - the drafting session's conversation history;
|
> - the drafting session's conversation history;
|
||||||
> - prior versions, version numbers, or a changelog;
|
> - prior versions, version numbers, or a changelog;
|
||||||
|
|
|
||||||
|
|
@ -80,23 +80,23 @@ Two different roles, both necessary, neither sufficient alone. A persona PASS is
|
||||||
**not** "ready for the editor" — it is "lands for the reader." This gate exists
|
**not** "ready for the editor" — it is "lands for the reader." This gate exists
|
||||||
because those are not the same thing.
|
because those are not the same thing.
|
||||||
|
|
||||||
## Truth source — the Maskinrommet writing-contract §C2
|
## Truth source — the in-tree craft checklist (mirrors Maskinrommet §C2 when available)
|
||||||
|
|
||||||
The checklist below is the **operationalized mirror of Maskinrommet
|
The **operative source of truth is the two-axis checklist below.** It ships
|
||||||
skrivekontrakt §C2.** §C2 documents the rule-set at the article-production level
|
in-tree, is self-contained, and is everything you need to judge. It is the
|
||||||
(what a human editor checks); this agent operationalizes it as the automated
|
operationalized mirror of the author's **Maskinrommet skrivekontrakt §C2**, which
|
||||||
pre-persona gate inside the plugin.
|
documents the same rule-set at the article-production level (what a human editor
|
||||||
|
checks). **§C2 itself does not ship with the plugin** (it lives in the author's
|
||||||
|
series repo), so for any adopter the in-tree checklist *is* the contract — you do
|
||||||
|
**not** need §C2 to run this gate.
|
||||||
|
|
||||||
> **Mirror rule (bidirectional, cardinal).** §C2 is the source of truth. A change
|
> **Mirror rule (only when you have §C2).** If — and only if — you are run with
|
||||||
> on either side MUST be mirrored to the other: if §C2 gains, drops, or reworks
|
> access to the live §C2 text (the author's own runs), read it and reconcile any
|
||||||
> a check, this agent's checklist follows, and vice-versa. The two must never
|
> drift before judging: §C2 is the upstream contract, this checklist its in-tree
|
||||||
> drift. This is the same relationship `references/longform-quality-rules.md`
|
> transcription, and the two should not diverge. This mirrors the relationship
|
||||||
> rule 8 has with §A (skeleton-before-prose) — the pipeline satisfies the
|
> `references/longform-quality-rules.md` rule 8 has with §A (skeleton-before-prose).
|
||||||
> writing contract *by construction*, and §C2 is the craft half of that contract.
|
> **Absent §C2 (the default for any adopter), judge against the in-tree checklist
|
||||||
|
> as the complete, authoritative source — its absence is not a gap.**
|
||||||
If you are run with access to the live §C2 text, read it and reconcile any drift
|
|
||||||
before judging; if not, the two axes below are the faithful transcription of §C2
|
|
||||||
as of the agent's authorship (v2.4.0) and you judge against them.
|
|
||||||
|
|
||||||
## The Two Axes
|
## The Two Axes
|
||||||
|
|
||||||
|
|
@ -236,9 +236,11 @@ before the Step 6 persona sweep. Operator decides fold-in; this is [OPERATØR].
|
||||||
7. **The operator gates, you recommend.** Your output is a report for KTG, not a
|
7. **The operator gates, you recommend.** Your output is a report for KTG, not a
|
||||||
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
pipeline stop. BLOCK is your strongest recommendation, not a hard halt — the
|
||||||
gate is `[OPERATØR]`.
|
gate is `[OPERATØR]`.
|
||||||
8. **§C2 is the source of truth.** If §C2 and this checklist disagree, §C2 wins
|
8. **The in-tree checklist is the operative source of truth.** It ships and is
|
||||||
and the checklist must be reconciled. Flag the drift; do not judge against a
|
self-contained — judge against it. §C2 (the upstream contract) is available
|
||||||
stale checklist.
|
only on the author's own runs; *if* you have it and it disagrees with this
|
||||||
|
checklist, flag the drift so the two can be reconciled. Absent §C2, its
|
||||||
|
absence is not a gap.
|
||||||
|
|
||||||
## Anti-Patterns
|
## Anti-Patterns
|
||||||
|
|
||||||
|
|
@ -261,9 +263,11 @@ before the Step 6 persona sweep. Operator decides fold-in; this is [OPERATØR].
|
||||||
## References
|
## References
|
||||||
|
|
||||||
Read these for the contract and the pipeline position:
|
Read these for the contract and the pipeline position:
|
||||||
- **Maskinrommet skrivekontrakt §C2** — the source of truth this checklist
|
- **Maskinrommet skrivekontrakt §C2** — the author's upstream contract this
|
||||||
mirrors (craft + architecture half of the writing contract; §A is the
|
in-tree checklist transcribes (craft + architecture half; §A is the
|
||||||
skeleton-before-prose half codified in `longform-quality-rules.md` rule 8).
|
skeleton-before-prose half codified in `longform-quality-rules.md` rule 8).
|
||||||
|
**Does not ship** — available only on the author's own runs; the in-tree
|
||||||
|
checklist above is the self-contained source for everyone else.
|
||||||
- `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md` — the broad Step 4
|
- `${CLAUDE_PLUGIN_ROOT}/references/longform-quality-rules.md` — the broad Step 4
|
||||||
quality pass; this agent is the *finer* craft+architecture gate that runs after
|
quality pass; this agent is the *finer* craft+architecture gate that runs after
|
||||||
it (rule 1 ≈ A5 overload; rule 3 ≈ some prose nits — defer the AI-tell face to
|
it (rule 1 ≈ A5 overload; rule 3 ≈ some prose nits — defer the AI-tell face to
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,24 @@ You run at **Step 6.5** of the `/linkedin:newsletter` pipeline — *after* the
|
||||||
in-session persona resonance sweep (Step 6), on a **frozen** draft, *before*
|
in-session persona resonance sweep (Step 6), on a **frozen** draft, *before*
|
||||||
lock — and you are invocable standalone via `/linkedin:headless-review`.
|
lock — and you are invocable standalone via `/linkedin:headless-review`.
|
||||||
|
|
||||||
|
## Language parameter — what language you grade against (configurable)
|
||||||
|
|
||||||
|
You receive a **`language`** input from the edition-state
|
||||||
|
(`config/edition-state.template.json`, default `"en"`). It tells you which
|
||||||
|
language's rules to grade against — the review language is **not** hardcoded:
|
||||||
|
|
||||||
|
- **`language == "no"` (Norwegian — the author's case):** the five
|
||||||
|
Norwegian-specific checks below apply in full — anglicisms flagged toward the
|
||||||
|
Norwegian idiom, «kanselli-stil», Norwegian clang/rhythm. This is the original
|
||||||
|
instantiation of the gate.
|
||||||
|
- **`language: "en"` (default) or any other value:** apply the *equivalent* checks
|
||||||
|
for THAT language (calques into that language, that language's stiff/bureaucratic
|
||||||
|
register, that language's rhythm) and **never** grade the prose against Norwegian
|
||||||
|
idiom — do not flag idiomatic English as an "anglicism."
|
||||||
|
|
||||||
|
If no `language` is supplied, assume `"en"`. Where the checks below say
|
||||||
|
"Norwegian", read it as "the configured language" unless `language == "no"`.
|
||||||
|
|
||||||
## Pipeline position
|
## Pipeline position
|
||||||
|
|
||||||
You are one of three **cold, headless re-readers** in the Step 6.5 package (with
|
You are one of three **cold, headless re-readers** in the Step 6.5 package (with
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,14 @@ editions.
|
||||||
|
|
||||||
This is the single most important rule of this agent.
|
This is the single most important rule of this agent.
|
||||||
|
|
||||||
|
> **Language parameter (configurable).** The review `language` is an input from
|
||||||
|
> the edition-state (default `"en"`). The gold standard is the approved editions
|
||||||
|
> **in the configured language**. The Norwegian-chronicle calibration below is the
|
||||||
|
> `language == "no"` instantiation (the author's case); for any other language,
|
||||||
|
> substitute that language's approved editions and read the Norwegian-specific
|
||||||
|
> notes (the em-dash habit, «kanselli-stil») as that language's equivalents. Never
|
||||||
|
> calibrate one language's voice against another's.
|
||||||
|
|
||||||
- The gold standard for Norwegian chronicle voice is the **approved Norwegian
|
- The gold standard for Norwegian chronicle voice is the **approved Norwegian
|
||||||
editions** (e.g. the series' approved Del 1 + Del 2). The caller passes the
|
editions** (e.g. the series' approved Del 1 + Del 2). The caller passes the
|
||||||
path(s); read them as the corpus before scrubbing.
|
path(s); read them as the corpus before scrubbing.
|
||||||
|
|
|
||||||
|
|
@ -1041,21 +1041,26 @@ command runs — see `commands/headless-review.md` for the full cold contract):
|
||||||
```
|
```
|
||||||
Record the frozen path; pass *that* path (not the live draft) to every reviewer.
|
Record the frozen path; pass *that* path (not the live draft) to every reviewer.
|
||||||
|
|
||||||
2. **Resolve the cold inputs.** The writing contract (`<serie>/../../docs/skrivekontrakt.md`
|
2. **Resolve the cold inputs.** The **review language** (`edition-state.language`,
|
||||||
→ plugin mirror → `references/longform-quality-rules.md`) and the active
|
default `en` — tells `language-reviewer` and `voice-scrubber` which language's
|
||||||
|
rules to grade against; Norwegian-specific checks fire only when `language: no`),
|
||||||
|
the writing contract *if it ships* (`<serie>/../../docs/skrivekontrakt.md` →
|
||||||
|
plugin mirror → `references/longform-quality-rules.md`; absent for an adopter,
|
||||||
|
the craft agents' in-tree checklists are self-contained), and the active
|
||||||
personas (`articles.NN.personas`, primær identified). Nothing else.
|
personas (`articles.NN.personas`, primær identified). Nothing else.
|
||||||
|
|
||||||
3. **Fan out the five archetypes in parallel** — issue them in a SINGLE message
|
3. **Fan out the five archetypes in parallel** — issue them in a SINGLE message
|
||||||
(multiple `Task` tool-uses) from THIS command layer in the foreground
|
(multiple `Task` tool-uses) from THIS command layer in the foreground
|
||||||
(principle 4), `subagent_type` namespaced:
|
(principle 4), `subagent_type` namespaced:
|
||||||
- `linkedin-studio:content-reviewer` — argument integrity (C1–C5)
|
- `linkedin-studio:content-reviewer` — argument integrity (C1–C5)
|
||||||
- `linkedin-studio:language-reviewer` — Norwegian language (L1–L5)
|
- `linkedin-studio:language-reviewer` — language quality (L1–L5; grades against `edition-state.language`, Norwegian-specific rules when `language: no`)
|
||||||
- `linkedin-studio:fact-reviewer` — cold re-verification (F1–F4, 🔴/🟡/🟢, incl. pivot premises)
|
- `linkedin-studio:fact-reviewer` — cold re-verification (F1–F4, 🔴/🟡/🟢, incl. pivot premises)
|
||||||
- `linkedin-studio:persona-reviewer` `mode: resonans` — **one call per active persona**
|
- `linkedin-studio:persona-reviewer` `mode: resonans` — **one call per active persona**
|
||||||
- `linkedin-studio:persona-reviewer` `mode: konverter` — **primær only** (hook)
|
- `linkedin-studio:persona-reviewer` `mode: konverter` — **primær only** (hook)
|
||||||
|
|
||||||
Each call's prompt carries ONLY the cold-contract inputs (frozen draft path,
|
Each call's prompt carries ONLY the cold-contract inputs (frozen draft path,
|
||||||
contract path, persona for the persona modes) + the instruction to ignore any
|
`language`, contract path if it ships, persona for the persona modes) + the
|
||||||
|
instruction to ignore any
|
||||||
framing about prior versions / cuts / pivots. **Never** paste history or
|
framing about prior versions / cuts / pivots. **Never** paste history or
|
||||||
summarize "what we changed" into a reviewer prompt — that is the context
|
summarize "what we changed" into a reviewer prompt — that is the context
|
||||||
pollution the package exists to eliminate.
|
pollution the package exists to eliminate.
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,15 @@
|
||||||
"visualAssets": "Per-article visual-asset record written by Step 7.5 (visual-assets phase). Runs BEFORE lock because render/build-linkedin.mjs picks up linkedin/NN/cover.png + the edition-config credit/caption when it builds POST.html — generating images after lock would force a re-render. Shape: { format: \"standard\" | \"carousel\"; cover: { brief, route, candidates[], approved, status }; figures: [ { id, brief, placement, status } ]; carousel: null | { source, pdf, status } }. format \"standard\" = cover + optional inline figures (cover.png is mandatory per the KTG cover-directive); format \"carousel\" = typografisk deck via render/build-carousel.mjs instead of cover+inline (cover/figures stay empty). route: \"mcp-image\" (default, via mcp__mcp-image__generate_image) | \"external\" (DALL·E / Midjourney / photographer → linkedin/NN/cover-raw.png). status ladder: pending → briefed → generated → approved. candidates[] holds the cover-v<N>-kandidat.png attempts; approved is the fixed approved name (\"cover.png\") once the operator-gate passes. figures[].id = \"fig1\"..; placement = section reference in NN-utkast.md (figures are referenced in the draft via  and uploaded manually in the LinkedIn editor — build-linkedin.mjs does NOT embed them). Naming convention: cover.png (approved, fixed — what build-linkedin.mjs reads) | cover-v<N>-kandidat.png (attempts) | cover-raw.png (optional external pre-edit source) | fig<N>.png (inline). credit + caption are recorded in <serie>/linkedin/image-credit-caption.md and flow into edition-config.json coverCredit + captions[NN].",
|
"visualAssets": "Per-article visual-asset record written by Step 7.5 (visual-assets phase). Runs BEFORE lock because render/build-linkedin.mjs picks up linkedin/NN/cover.png + the edition-config credit/caption when it builds POST.html — generating images after lock would force a re-render. Shape: { format: \"standard\" | \"carousel\"; cover: { brief, route, candidates[], approved, status }; figures: [ { id, brief, placement, status } ]; carousel: null | { source, pdf, status } }. format \"standard\" = cover + optional inline figures (cover.png is mandatory per the KTG cover-directive); format \"carousel\" = typografisk deck via render/build-carousel.mjs instead of cover+inline (cover/figures stay empty). route: \"mcp-image\" (default, via mcp__mcp-image__generate_image) | \"external\" (DALL·E / Midjourney / photographer → linkedin/NN/cover-raw.png). status ladder: pending → briefed → generated → approved. candidates[] holds the cover-v<N>-kandidat.png attempts; approved is the fixed approved name (\"cover.png\") once the operator-gate passes. figures[].id = \"fig1\"..; placement = section reference in NN-utkast.md (figures are referenced in the draft via  and uploaded manually in the LinkedIn editor — build-linkedin.mjs does NOT embed them). Naming convention: cover.png (approved, fixed — what build-linkedin.mjs reads) | cover-v<N>-kandidat.png (attempts) | cover-raw.png (optional external pre-edit source) | fig<N>.png (inline). credit + caption are recorded in <serie>/linkedin/image-credit-caption.md and flow into edition-config.json coverCredit + captions[NN].",
|
||||||
"personas": "Per-article resolved reader-persona set (input config), written/confirmed in Step 1. This makes personas configurable PER ARTIFACT, not just from one global plugin library: Step 1 resolves them in order — (1) already present here → use as-is; (2) <serie>/linkedin/personas.md (per-series file) → load; (3) plugin config/personas.local.md (or personas.template.md) library → select a subset; (4) none/insufficient → DEFINE interactively via AskUserQuestion. Exactly one entry has tier \"primær\" (the rest \"sekundær\"); «primær trumfer» on conflict. This set feeds BOTH the in-session sweep (Step 6) and the headless package (Step 6.5 / persona-reviewer). Each entry: { name, tier: \"primær\" | \"sekundær\", rolle, avkobler, overbeviser, ekspertise, sjargong, source: \"edition-state\" | \"series-file\" | \"plugin-library\" | \"interactive\" }. Default []: resolved on first Step 1.",
|
"personas": "Per-article resolved reader-persona set (input config), written/confirmed in Step 1. This makes personas configurable PER ARTIFACT, not just from one global plugin library: Step 1 resolves them in order — (1) already present here → use as-is; (2) <serie>/linkedin/personas.md (per-series file) → load; (3) plugin config/personas.local.md (or personas.template.md) library → select a subset; (4) none/insufficient → DEFINE interactively via AskUserQuestion. Exactly one entry has tier \"primær\" (the rest \"sekundær\"); «primær trumfer» on conflict. This set feeds BOTH the in-session sweep (Step 6) and the headless package (Step 6.5 / persona-reviewer). Each entry: { name, tier: \"primær\" | \"sekundær\", rolle, avkobler, overbeviser, ekspertise, sjargong, source: \"edition-state\" | \"series-file\" | \"plugin-library\" | \"interactive\" }. Default []: resolved on first Step 1.",
|
||||||
"headlessReview": "Per-article headless-review record written by Step 6.5 (headless-review phase). Runs AFTER the in-session persona sweep (Step 6) and BEFORE lock (Step 8), on a FROZEN snapshot of the publish-ready (or pivoted) draft, fanned out from the command layer (foreground) or invoked standalone via /linkedin:headless-review in a fresh/cold session. Five archetypes judge independently with NO drafting-session context: content-reviewer (argument integrity), language-reviewer (Norwegian language), fact-reviewer (cold re-verification incl. claims a late pivot bolted on), persona-reviewer mode=resonans (per active persona), persona-reviewer mode=konverter (primær, hook only). The consolidated report is surfaced to the operator via SendUserFile; the operator decides which flags fold in. Shape: { frozenDraft, reviewers: { content, language, fact, personaResonance, personaConversion } (each { reportPath, summary, status }), consolidatedReport, foldedIn, waived, status }. status ladder: pending → run → folded. null until Step 6.5 runs. This is the adversarial-independence companion to the in-session gates (editorialReview, personaSweep, factcheckLog) — deliberately redundant: a cold reader catches what the framing-biased in-session pass missed.",
|
"headlessReview": "Per-article headless-review record written by Step 6.5 (headless-review phase). Runs AFTER the in-session persona sweep (Step 6) and BEFORE lock (Step 8), on a FROZEN snapshot of the publish-ready (or pivoted) draft, fanned out from the command layer (foreground) or invoked standalone via /linkedin:headless-review in a fresh/cold session. Five archetypes judge independently with NO drafting-session context: content-reviewer (argument integrity), language-reviewer (Norwegian language), fact-reviewer (cold re-verification incl. claims a late pivot bolted on), persona-reviewer mode=resonans (per active persona), persona-reviewer mode=konverter (primær, hook only). The consolidated report is surfaced to the operator via SendUserFile; the operator decides which flags fold in. Shape: { frozenDraft, reviewers: { content, language, fact, personaResonance, personaConversion } (each { reportPath, summary, status }), consolidatedReport, foldedIn, waived, status }. status ladder: pending → run → folded. null until Step 6.5 runs. This is the adversarial-independence companion to the in-session gates (editorialReview, personaSweep, factcheckLog) — deliberately redundant: a cold reader catches what the framing-biased in-session pass missed.",
|
||||||
"pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default []."
|
"pivots": "Per-article pivot log (Endring 9c). A pivot is a substantive change to a draft AFTER a gate had already cleared — e.g. a new argument anchor / section added late (the Del 4 Security Champions case: +~530 words, 2 new sections, +42 %). Each /linkedin:pivot invocation appends one entry and moves currentPhase back so the cleared gates (Steps 5–6.5) re-run on the pivoted version before lock. Heuristic (documented, checked at the Step 8 lock precondition): if the current draft's word count differs > 20 % from the version that last cleared Step 6, OR it has > 2 new sections, a pivot-reopen is suggested/required. Each entry: { timestamp, reason, fromPhase, toPhase, wordCountBefore, wordCountAfter, deltaPct, newSections, gatesToRerun: [phase…] }. Default [].",
|
||||||
|
"language": "Review language for this series/edition (additive, default \"en\"). Threads into the long-form review agents so they grade against THIS language's rules: language-reviewer applies Norwegian-specific checks (anglicism→Norwegian idiom, «kanselli-stil») only when language == \"no\"; voice-scrubber's gold standard is the approved editions IN this language; any other value → the agents apply that language's equivalents and never grade prose against Norwegian idiom. \"no\" = Norwegian (the author's case). Resolved at Step 1 / load-context and passed to the language-dependent agents."
|
||||||
},
|
},
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"series": {
|
"series": {
|
||||||
"slug": "<series-slug>",
|
"slug": "<series-slug>",
|
||||||
"title": "<Series title>"
|
"title": "<Series title>"
|
||||||
},
|
},
|
||||||
|
"language": "en",
|
||||||
"currentArticle": "01",
|
"currentArticle": "01",
|
||||||
"currentPhase": "load-context",
|
"currentPhase": "load-context",
|
||||||
"updatedAt": "<ISO-8601 timestamp>",
|
"updatedAt": "<ISO-8601 timestamp>",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue