Skip to content

Compact list blocks: a sub-block no longer loosens a tight list#227

Draft
dereuromark wants to merge 2 commits into
masterfrom
proposal/tight-list-sub-blocks
Draft

Compact list blocks: a sub-block no longer loosens a tight list#227
dereuromark wants to merge 2 commits into
masterfrom
proposal/tight-list-sub-blocks

Conversation

@dereuromark

Copy link
Copy Markdown
Contributor

Draft / proposal. Always-on. This is a deliberate divergence from canonical djot for discussion — see "Divergence" at the end. The official djot test suite stays green (it doesn't pin these cases), but djot.js would render the lists below loose.

The problem: you can't have a tight list item that contains a block

djot has two rules that collide:

  1. a block (quote, code, div, heading, sub-list) inside a list item needs a blank line before it, and
  2. a blank line inside an item makes the whole list loose (every item gets <p> wrappers + vertical spacing).

So any list whose items carry a sub-block is forced loose. There is no way to write a compact checklist-with-notes or steps-with-code. That's the itch this scratches.

The change

A blank line is still required to start the block (block recognition and the uniformity principle are unchanged). It just no longer loosens the list when the indented content opens a block. Loose is still triggered by a genuine second prose paragraph, or a blank line between items.

Net effect, on one example:

- One

  > Quote
  <li>
- <p>One</p>
+ One
  <blockquote>
  <p>Quote</p>
  </blockquote>
  </li>

Only the lead <p> placement changes — the block structure is identical.

Showcase — where it comes in handy

Checklist with a note per task (deploy runbook, review checklist):

- [ ] Deploy the service

  > Needs the new env vars set first
- [x] Run migrations

→ a tight task list; the note sits with its item instead of the whole list blowing out to loose/spaced.

<ul class="task-list">
<li>
<input disabled="" type="checkbox"/>
Deploy the service
<blockquote>
<p>Needs the new env vars set first</p>
</blockquote>
</li>
<li>
<input disabled="" type="checkbox" checked=""/>
Run migrations
</li>
</ul>

Steps with a code block (install / build instructions):

- Build the image

  ```sh
  docker build -t app .
  • Push it
```html
<ul>
<li>
Build the image
<pre><code class="language-sh">docker build -t app .
</code></pre>
</li>
<li>
Push it
</li>
</ul>

The code attaches to its step, list stays tight. Same for a :::note admonition, a sub-table, or a quote under each item.

What is deliberately not changed (still loose, correctly)

A real second paragraph in an item:

- First point.

  A full second paragraph of explanation.
- Second point.

→ stays loose (<p>First point.</p> + <p>…explanation.</p>). And a blank line between items still loosens. Only blank-then-block is affected.

Why this respects uniformity (the djot principle)

Uniformity is about block structure — "a single paragraph stays a single paragraph when embedded." This change touches only tight-vs-loose rendering (whether the lead paragraph prints its <p> tag), which djot already varies by tight/loose. No block is recognized differently; embedding is unchanged. So it does not violate the principle that motivated djot.

Divergence (honest)

Canonical djot renders the showcase lists loose; this renders them tight. The official test suite passes because it does not pin these specific loose-list-with-sub-block cases, but this is a behavioral difference from djot.js on those inputs. Hence: draft, for discussion on whether this belongs in djot-php (always-on), behind a flag, or proposed upstream.

Implementation

One guard broadened in BlockParser: the "blank line + indented content" loosening check goes from "is a list marker" to "opens any structured block" (reusing startsNewBlockSignificant). Between-items loosening untouched; the renderer already strips the lead <p> for tight items, so no renderer change. New CompactListBlocksTest; one round-trip test updated to the tight output.

A blank line is still required to START a block inside a list item (block
recognition and the uniformity principle are unchanged), but that blank line no
longer forces the list LOOSE when the indented content opens a block (sub-list,
block quote, fenced code, fenced div, heading, table). Only a genuine second
prose paragraph, or a blank line between items, makes the list loose.

Result: an item can carry a sub-block while staying tight (lead text inline, no
<p> ceremony) -- the common case for checklists with notes, steps with code, etc.

This changes only the tight/loose RENDERING, never the block structure, so it
does not touch uniformity. It is a deliberate divergence from canonical djot
(which renders these lists loose); the official test suite stays green because it
does not pin these particular loose cases.

- BlockParser: the "blank + indented content" loose guard is broadened from
  "is a list marker" to "opens any structured block" (reuses
  startsNewBlockSignificant). Between-items loosening is unchanged.
- Renderer already strips the lead <p> for tight items, so no renderer change.
@codecov

codecov Bot commented Jun 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.87%. Comparing base (d4de671) to head (ed30a7d).

Additional details and impacted files
@@            Coverage Diff            @@
##             master     #227   +/-   ##
=========================================
  Coverage     91.87%   91.87%           
- Complexity     3512     3513    +1     
=========================================
  Files           105      105           
  Lines          9944     9946    +2     
=========================================
+ Hits           9136     9138    +2     
  Misses          808      808           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Add a 'Compact List Blocks' section explaining that a sub-block after a blank
line keeps the list tight (vs loose in canonical djot), with a before/after
example and the still-loose cases (second prose paragraph, blank between items).
Flagged as a djot-php divergence.
@dereuromark

Copy link
Copy Markdown
Contributor Author

This would be the ideal solution to tight lists.
Until the upstream specs are approved and adjusted, we can go with #228 for now though as non-invasive workaround.
Putting this one on hold then for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant