Skip to content

Drop the trailing Write here and review the empty-block hint grey #371

Description

@HMarzban

Related

No open issue asks for this change. The contrast rule is WCAG 2.2 Understanding SC 1.4.3 Contrast (Minimum).

What happened

Edward Saperia asked for two changes in chat on 2026-09-26.

He wrote: "issue: can you remove the last "> Write here" from this". The phrase "this" is the empty-block hint.

He also wrote: "also make it a bit lighter grey".

The line in his screenshot is House London #2: 26... > Submissions > Submission Title > Write here.

A pin emoji at the start belongs to the heading text.

A grey bar above the hint reads 100-250 word abstract goes here.

That bar is document text. This change leaves it alone.

Please drag the screenshot from that chat onto this issue.

What we expect

A top-level empty paragraph shows the heading chain only.

The example line becomes House London #2: 26... > Submissions > Submission Title.

The grey stays readable under WCAG 2.2 SC 1.4.3.

A lighter grey than today fails 4.5:1 on the light themes.

The maintainer picks the shade. The passing range is in Findings.

Steps to reproduce

  1. Open a pad you can edit.
  2. Add a few headings, then an empty paragraph under the last one.
  3. Put the caret in that empty paragraph.
  4. Read the grey empty-block hint. It ends with > Write here.

Findings

Who owns the text. Read from code.

The webapp builds the trailing segment.

resolveTailSegment returns Write here from the constant PLACEHOLDER_TEXT.paragraph.

The pad calls buildBreadcrumbPlaceholder with scope: 'top-level' in TipTap.tsx.

The placeholder extension paints the string the callback returns.

It stores that string on data-placeholder. Its default is Write something ….

The pad replaces that default. The published package stays unchanged.

What the heading chain is for. Read from code.

The editor schema is flat (heading block*).

buildAncestorChain keeps the headings that sit above the caret.

That chain names the section the empty paragraph belongs to.

The trailing segment only names the block as a paragraph.

Keep the heading chain. Drop the trailing segment.

Heading N, Subtitle, and Enter document name use other branches. Leave them.

List, To-do, Quote, and Write code are the nested labels. Leave them.

The pad wires only top-level. all-blocks appears only in the unit test.

If a paragraph has no heading before it, dropping the tail leaves an empty string.

The extension then draws no hint. A normal pad starts with a heading, so a later paragraph still has a chain.

Where else the words appear. Read from code.

buildBreadcrumbPlaceholder.test.ts asserts Write here on several lines.

The chat composer uses Write a message... and Add a comment… in useTiptapEditor.ts.

No Cypress spec, README, or GIF asserts Write here.

Colour. Measured in headless Chromium with Playwright. Each theme used the hex values in globals.scss.

The page used color-mix(in oklch, var(--color-base-content) <mix>, transparent).

getComputedStyle supplied the colour. A 1px canvas composited it onto the sheet.

The ratio is the WCAG luminance of that pixel against --color-base-100.

Pad rule, read from code: --prose-placeholder-mix: 50% on .editorWrapper .ProseMirror in _blocks.scss.

The paint rule is .is-empty::before in _prose-mirror-body.scss.

The chat composer hardcodes a 40% mix on p.is-empty::before in _chat-editor.scss.

One pad variable colours every empty-block hint in the pad.

A change to it also changes Heading, Subtitle, List, Quote, and Write code.

The title hint uses --muted-foreground, a separate 50% mix in globals.scss.

Leave that token alone unless the maintainer says otherwise.

Theme Resolved colour at 50% Composited on the sheet Ratio at 50% Ratio at 40% Lowest mix at 4.5:1
docsplus oklch(0.207678 0.0398189 265.767 / 0.5) #878b94 on #ffffff 3.414 2.554 60% (4.687)
docsplus-dark oklch(0.927576 0.00579101 none / 0.5) #7b7c84 on #0b1220 4.510 3.274 50% (4.510)
docsplus-dark-hc oklch(0.968254 0.00681902 none / 0.5) #84858f on #0f172a 4.874 3.542 48% (4.541)
docsplus-graphite oklch(0.190957 0.0174849 none / 0.5) #8a8688 on #f9fafc 3.439 2.572 59% (4.571)
docsplus-graphite-dark oklch(0.930605 0.00578615 none / 0.5) #7c797c on #0c0d10 4.518 3.266 50% (4.518)
docsplus-paper oklch(0.185189 0.0142021 none / 0.5) #898483 on #fbf8f4 3.485 2.595 58% (4.528)
docsplus-paper-dark oklch(0.92493 0.0104246 none / 0.5) #7f7978 on #110e0b 4.497 3.263 51% (4.579)

The Understanding page says SC 1.4.3 covers placeholder text.

It requires 4.5:1 for normal text and 3:1 for large text.

Large text means 18 point, or 14 point bold.

The empty paragraph has no font-size rule, so this hint needs 4.5:1.

The live font size in the app was read from code, not measured.

A lighter grey is a lower mix. More of the sheet shows through.

On the light themes, the current 50% mix is already under 4.5:1.

The 40% column is the lighter example. It is also the chat composer's mix.

No mix under 50% meets 4.5:1 on the light themes.

One mix that passes 4.5:1 on all seven themes is 60% or higher.

That mix is darker than the current 50%.

On docsplus, 45% is 2.956:1, so it also misses the 3:1 large-text bar.

New colours become root tokens in globals.scss. That is the design-system change protocol.

Do not write a new color-mix on a component.

Duplicates. Read from GitHub and from the repo.

Issue search and a discussion search found no matching request.

Issue #45 is a closed 2023 report about heading hint font size. It is a different change.

No changelog entry explains the tail.

The comment on resolveTailSegment says a heading tail shows the level the reader is about to write.

The paragraph tail sits in the same map. No ruling says that tail must stay.

Next step: drop the trailing segment in the webapp, and wait for a shade that still passes 4.5:1.

Acceptance criteria

  • On a top-level empty paragraph, the trailing segment Write here is gone.
  • The heading chain before it stays, with the same > separators and the same truncation.
  • Heading N, Subtitle, Enter document name, List, To-do, Quote, and Write code stay.
  • The chat composer hints and their 40% mix stay as they are.
  • The maintainer picks the grey. Ship it only when every theme is at least 4.5:1 against --color-base-100.
  • A mix under 60% fails that bar on at least one theme. Do not ship a lighter mix that fails.
  • The chosen colour is a token in globals.scss. It is not a new color-mix in a component.
  • If the pad variable changes, a comment names the other empty-block hints that change with it.
  • buildBreadcrumbPlaceholder.test.ts fails on the old tail before the edit and passes after it.
  • Run only that test file. Do not run the whole webapp Jest suite.

Blocked by

None. The trailing-segment edit can start. The grey waits for a maintainer pick.

Agent brief

Type: AFK for the trailing segment and its test. HITL for the grey. The maintainer picks the shade before any colour edit.

Category: enhancement

Current behavior: A top-level empty paragraph shows the heading chain, then > Write here. The pad paints that hint at a 50% mix of --color-base-content. On the light themes that mix is under 4.5:1.

Desired behavior: The heading chain stays. The trailing segment is gone. The grey meets 4.5:1 in every theme. The maintainer chooses the shade inside the passing range.

Where to start: apps/webapp/src/components/TipTap/placeholders/buildBreadcrumbPlaceholder.ts (PLACEHOLDER_TEXT, resolveTailSegment, buildBreadcrumbPlaceholder). apps/webapp/src/components/TipTap/TipTap.tsx (Placeholder.configure). apps/webapp/src/styles/_blocks.scss (--prose-placeholder-mix). apps/webapp/src/styles/_prose-mirror-body.scss (.is-empty::before). apps/webapp/src/styles/_chat-editor.scss (leave the 40% rule). apps/webapp/src/styles/globals.scss (a new token, and --muted-foreground). extensions/extension-placeholder/src/placeholder.ts (Placeholder) is read only. apps/webapp/src/components/TipTap/placeholders/buildBreadcrumbPlaceholder.test.ts. Search by symbol. File names are hints as of 2026-09-29.

Rules that apply: .cursor/docs/design-system.md Change protocol. New colours become root tokens in globals.scss. apps/webapp/src/components/TipTap/CLAUDE.md §Editor Performance. Keep @docs.plus/extension-placeholder. Do not switch to the built-in placeholder. AGENTS.md §Test Policy allows this test because it pins a reported request. Update the prose row in the design-system catalog if the mix changes.

Verify: Open a pad and put the caret in an empty paragraph under headings. The hint has no Write here. The heading chain is unchanged. If the grey changes, check all seven themes against the table. Run only buildBreadcrumbPlaceholder.test.ts. Do not run the whole webapp Jest suite.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    EditorTiptap & ProsemirrorUIenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions