Skip to content

Commit 3cae615

Browse files
cscheidclaude
andcommitted
docs(plan): resolve list-item JSON representation for block attrs (bd-aeyss6p5)
Record the design study for carrying a list-item block attribute through JSON. List items are arrays (`Vec<Blocks>`), not objects, so the `Para` extra-`attr`-key trick doesn't apply. The source-location / attr-source work already solved "array-shaped Pandoc structure needs out-of-band data" via parallel sibling keys on the enclosing object node (`a`, `rowsS`, `cellsS`, `bodyS`, `captionS`, …, each parallel-indexed to its array). Apply the same pattern: hoist each item's trailing `Inline::Attr` into a parallel `itemAttr` key on the list node; the reader folds it back. Mirrors the table `rowsS` precedent; keeps item arrays Pandoc-valid; round-trips. Informs the bd-aeyss6p5 implementation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f46c879 commit 3cae615

1 file changed

Lines changed: 60 additions & 1 deletion

File tree

claude-notes/plans/2026-06-17-block-level-attrs-inline-attr.md

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,66 @@ arms are left as-is.
7878
emitted — the docs/ page needs no "no longer emitted" note.
7979
- [ ] List items (`<li class>`) — distinct hoist mechanism (attr rides on the
8080
item's child block; the `<li>` writer must hoist it, composing with the
81-
incremental `fragment` class). Later sub-task.
81+
incremental `fragment` class). Later sub-task (**bd-aeyss6p5**). JSON
82+
representation resolved below.
83+
84+
## List-item JSON representation — study (for bd-aeyss6p5)
85+
86+
The `Para` `attr`-key trick relies on a `Para` being a JSON **object**
87+
(`{t,c,…}`) onto which an extra key can hang. A list item is **not** an object:
88+
`BulletList`/`OrderedList` are `content: Vec<Blocks>` (`crates/quarto-pandoc-types/src/block.rs:138-148`),
89+
and in JSON each item is a bare `[Block,…]` **array** inside the list node's `c`.
90+
There is no per-item object to carry an `attr` key.
91+
92+
**This exact problem is already solved in the source-location / attr-source
93+
work**, via a single reusable pattern:
94+
95+
> When a Pandoc sub-structure is array-shaped (no object to hold an extra key),
96+
> carry the Quarto-only data in a **parallel sibling key on the nearest
97+
> enclosing object node**, shaped to mirror the array (parallel-indexed), and
98+
> read it back from that key. Pandoc ignores unknown object keys (proven for
99+
> `Para.attr`; `a`/`rowsS`/`captionS` already ship and round-trip).
100+
101+
Concrete precedents (all in `crates/pampa/src/writers/json.rs` + reader):
102+
- **Attr source** — the attr triple `[id,[class],[[k,v]]]` is pure arrays/strings,
103+
so its source rides in a sibling `a` key as `AttrSourceJson { classes:[
104+
parallel to classes…], id, kvs:[… parallel to kvs…] }` (`json.rs:137,676`,
105+
stream `:2020`; reader `read_attr_source` at `readers/json.rs:635`).
106+
- **Tables** — rows/cells/heads/bodies/feet are all array-shaped; their
107+
out-of-band data rides in sibling keys `a`, `rowsS`, `cellsS`, `headS`,
108+
`bodyS`, `bodiesS`, `captionS`, each an array **parallel-indexed** to its data
109+
array (writer `stream_write_table_head_source` `:2435`, `stream_write_row_source`
110+
`:2399`; reader threads `obj.get("rowsS")` / `get("bodyS")` / `get("captionS")`
111+
at `readers/json.rs:1668,1726,1787,2037`). This is the closest analog to a
112+
list: a `TableHead` is `[attr,[rows]]` and per-row data lives in `rowsS`
113+
parallel to the rows — *exactly* the "per-item data parallel to an item array"
114+
shape we need.
115+
116+
**Resulting design for list items (recommended):**
117+
- **AST (no type change):** the item attr stays a **trailing `Inline::Attr` in
118+
the item's last block** — same canonical shape as `Para`, and what `- item
119+
{.foo}` would naturally parse to.
120+
- **JSON writer:** per item, run `split_trailing_block_attr` on the item's last
121+
block; hoist the merged `Attr` into a **parallel sibling key** on the list node
122+
(mirroring `rowsS`; name e.g. `itemAttr`/`cAttr` — TBD), an array
123+
parallel-indexed to `c`, each entry an `Attr` triple or `null`; emit the item's
124+
blocks **without** the trailing attr. Stripping here also fixes the
125+
precedence trap — the inner `Para`/`Plain` writer never sees the attr, so the
126+
class lands on `<li>`, not an inner `<p>`.
127+
```json
128+
{"t":"BulletList","c":[[…item0…],[…item1…]],"itemAttr":[["",["foo"],[]],null],"s":}
129+
```
130+
- **JSON reader:** read the parallel key; fold each non-null entry back into a
131+
trailing `Inline::Attr` on the matching item's last block — exactly like the
132+
`Para` `attr` fold-back (`readers/json.rs` Para arm) and the table `rowsS`
133+
threading.
134+
- **html.rs / React:** hoist to `<li class>`, composing with the incremental
135+
`fragment` class (`list_item_open` at `html.rs:1272`; React BulletList/
136+
OrderedList both paths).
137+
138+
Net: item arrays stay Pandoc-valid (Pandoc ignores `itemAttr`), the preview
139+
reads the sibling key, and it round-trips — all consistent with the established
140+
table/attr-source machinery rather than a new ad-hoc channel.
82141
- [ ] Wire the actual consumer: bd-38ioql41 reveal figure caption emits a
83142
`Paragraph[..caption.., Attr{.caption}]`; add the q2-slides preview parity
84143
check there.

0 commit comments

Comments
 (0)