Threaded review discussion with flat host-supplied comments, reply submission, and resolve/reopen events.
Install @wyrd-company/react or the Flutter package and use it directly —
your DESIGN.md does the styling.
catalog/contracts/comment-thread.ymlsource of truth · verbatim
$schema: https://thewyrdingway.dev/schemas/component-contract.schema.ymlcontractVersion: 0.1.0identity:name: CommentThreadtag: wyrd-comment-threadpackage: wyrd.component.comment-threadtitle: CommentThreadsummary: Threaded review discussion with flat host-supplied comments, reply submission, and resolve/reopen events.relationships:related-to: - quality-parity-and-gatesclassification:category: collaborationcomposition: collectioninteractive: truetarget-strategy:react: requiredflutter: requiredsurface:properties: - name: thread-idtype:kind: stringrequired: trueserializable: truesummary: Opaque host-supplied thread identifier. - name: statustype:kind: enumvalues: - open - resolvedrequired: falsedefault: openreflects: trueserializable: truesummary: Host-supplied thread status. - name: commentstype:kind: arrayitem:kind: objectfields:id:kind: stringauthor-id:kind: stringauthor-name:kind: stringtimestamp:kind: stringtimestamp-label:kind: stringbody-format:kind: enumvalues: - plain-textbody:kind: stringdecision:kind: enumvalues: - none - approve - request-changes - comment - suggestionedited:kind: booleancan-edit:kind: booleancan-delete:kind: booleanrequired: falsedefault: []serializable: truesummary: Flat ordered comments in display order, structurally mirroring the comment contract. - name: reply-valuetype:kind: stringrequired: falsecontrolled: trueserializable: truesummary: Controlled reply composer value. - name: default-reply-valuetype:kind: stringrequired: falsedefault: ""serializable: truesummary: Initial uncontrolled reply composer value. - name: can-replytype:kind: booleanrequired: falsedefault: trueserializable: truesummary: Shows the reply composer when true. - name: can-resolvetype:kind: booleanrequired: falsedefault: trueserializable: truesummary: Shows resolve/reopen affordance when true.slots: []events: - name: reply-changepayload:thread-id:kind: stringvalue:kind: stringbubbles: truecomposed: truecancelable: falsesummary: Emitted when the reply composer value changes. - name: reply-submitpayload:thread-id:kind: stringbody:kind: stringbubbles: truecomposed: truecancelable: truesummary: Emitted when the user submits a non-empty reply. - name: resolvepayload:thread-id:kind: stringbubbles: truecomposed: truecancelable: truesummary: Emitted when the user requests resolving an open thread. - name: reopenpayload:thread-id:kind: stringbubbles: truecomposed: truecancelable: truesummary: Emitted when the user requests reopening a resolved thread. - name: comment-editpayload:thread-id:kind: stringcomment-id:kind: stringbubbles: truecomposed: truecancelable: truesummary: Re-emitted when a child comment edit affordance is activated. - name: comment-deletepayload:thread-id:kind: stringcomment-id:kind: stringbubbles: truecomposed: truecancelable: truesummary: Re-emitted when a child comment delete affordance is activated.states: - name: opensource: propertysummary: Thread is open. - name: resolvedsource: propertysummary: Thread is resolved. - name: replyingsource: interactionsummary: Reply composer has focus or content.parts: - name: rootsummary: Thread root. - name: headersummary: Status and resolve/reopen action row. - name: statussummary: Thread status text. - name: listsummary: Comment list/feed. - name: itemsummary: Comment list item. - name: composersummary: Reply composer region. - name: reply-inputsummary: Reply text input. - name: reply-submitsummary: Reply submit button. - name: status-actionsummary: Resolve/reopen button.tokens:background: color.surfaceforeground: color.on-surfacemuted: color.on-surface-variantborder-color: color.outlinecomposer-background: color.surface-containeraction-background: color.primaryaction-foreground: color.on-primaryradius: rounded.mdpadding-x: spacing.mdpadding-y: spacing.mdgap: spacing.smmessages: - key: comment-thread.replydescription: Label for the reply composer. - key: comment-thread.reply-placeholderdescription: Placeholder text for the reply composer. - key: comment-thread.submit-replydescription: Button label for submitting a reply. - key: comment-thread.resolvedescription: Button label for resolving an open thread. - key: comment-thread.reopendescription: Button label for reopening a resolved thread. - key: comment-thread.status.opendescription: Status label for an open thread. - key: comment-thread.status.resolveddescription: Status label for a resolved thread.obligations:accessibility:role: listname: Thread id and status identify the comment thread; hosts may wrap with a stronger label when needed.keyboard: - Tab moves through resolve/reopen action, child comment actions, reply input, and submit button. - Enter or Space activates focused buttons. - The reply input accepts normal multiline text entry.semantics: - Web projections render thread comments as a native list of article-like comment items. - Resolve/reopen and reply submit controls are buttons named by component-owned localized messages. - Reply input is labelled by the component-owned reply message. - Empty reply submissions are ignored locally and do not dispatch reply-submit.behavior:model: collectioninteractions: - Comments render in host-supplied order. - reply-value ?? default-reply-value determines the effective composer text. - Editing the composer emits reply-change with thread-id and value. - Submitting non-empty composer text emits reply-submit with thread-id and body. - Resolve emits resolve for open threads; reopen emits reopen for resolved threads. - Child comment edit/delete actions are re-emitted with thread-id and comment-id.hostEffects: - Dispatch reply-change event. - Dispatch reply-submit event. - Dispatch resolve or reopen event. - Dispatch child comment edit/delete events. - Hosts own persistence, identity, permissions, thread status transitions, and reply clearing.conditions: - rtl - reduced-motion - cvd-deuteranopia - density-compact - text-expansionsdui:status: serializableserializableProperties: - thread-id - status - comments - reply-value - default-reply-value - can-reply - can-resolveserializableSlots: []eventBindings: - reply-change - reply-submit - resolve - reopen - comment-edit - comment-deletenotes: CommentThread serializes flat comments and interaction bindings. It does not own workflow state, identity, or persistence.examples: - name: openstate: openproperties:thread-id: thread-route-delaystatus: opencomments: - id: comment-route-delay-copyauthor-id: author-rinauthor-name: Rintimestamp: "2026-07-04T16:15:00.000Z"timestamp-label: Today at 4:15 PMbody-format: plain-textbody: Delay copy should identify the affected service window before dispatch signs off.decision: request-changesedited: falsecan-edit: falsecan-delete: falsedefault-reply-value: ""slots: {} - name: resolvedstate: resolvedproperties:thread-id: thread-coordinator-lockstatus: resolvedcomments: - id: comment-coordinator-lockauthor-id: author-maraauthor-name: Maratimestamp: "2026-07-04T16:20:00.000Z"body-format: plain-textbody: Locked coordinator field matches this scenario.decision: approveedited: truecan-edit: truecan-delete: falseslots: {}overrides:react:properties:thread-id: threadIdreply-value: replyValuedefault-reply-value: defaultReplyValuecan-reply: canReplycan-resolve: canResolveevents:reply-change: onReplyChangereply-submit: onReplySubmitcomment-edit: onCommentEditcomment-delete: onCommentDeleteflutter:properties:thread-id: threadIdreply-value: replyValuedefault-reply-value: defaultReplyValuecan-reply: canReplycan-resolve: canResolveevents:reply-change: onReplyChangereply-submit: onReplySubmitcomment-edit: onCommentEditcomment-delete: onCommentDelete
01
Examples
Generated from the contract's examples block.
Previews are the published @wyrd-company/react component, themed by the
brand's role tokens — flip the site's light/dark toggle and they follow.
openstate: open
Usage
import { WyrdCommentThread } from"@wyrd-company/react";
<WyrdCommentThread threadId="thread-route-delay" status="open" comments={[{"id":"comment-route-delay-copy","author-id":"author-rin","author-name":"Rin","timestamp":"2026-07-04T16:15:00.000Z","timestamp-label":"Today at 4:15 PM","body-format":"plain-text","body":"Delay copy should identify the affected service window before dispatch signs off.","decision":"request-changes","edited":false,"can-edit":false,"can-delete":false}]} defaultReplyValue="" />
// events: onReplyChange, onReplySubmit, onResolve, onReopen, onCommentEdit, onCommentDelete
import'package:wyrd_flutter/wyrd_flutter.dart';
WyrdCommentThread(
threadId: 'thread-route-delay',
status: 'open',
comments: [{"id":"comment-route-delay-copy","author-id":"author-rin","author-name":"Rin","timestamp":"2026-07-04T16:15:00.000Z","timestamp-label":"Today at 4:15 PM","body-format":"plain-text","body":"Delay copy should identify the affected service window before dispatch signs off.","decision":"request-changes","edited":false,"can-edit":false,"can-delete":false}],
defaultReplyValue: '',
)
// events: onReplyChange, onReplySubmit, onResolve, onReopen, onCommentEdit, onCommentDelete
Flat ordered comments in display order, structurally mirroring the comment contract.
reply-value
string
undefined
Controlled reply composer value.
default-reply-value
string
Initial uncontrolled reply composer value.
can-reply
boolean
true
Shows the reply composer when true.
can-resolve
boolean
true
Shows resolve/reopen affordance when true.
03
Slots & events
Event
Payload
Summary
reply-change
thread-id, value
Emitted when the reply composer value changes.
reply-submit
thread-id, body
Emitted when the user submits a non-empty reply.
resolve
thread-id
Emitted when the user requests resolving an open thread.
reopen
thread-id
Emitted when the user requests reopening a resolved thread.
comment-edit
thread-id, comment-id
Re-emitted when a child comment edit affordance is activated.
comment-delete
thread-id, comment-id
Re-emitted when a child comment delete affordance is activated.
04
Tokens
The contract's token bindings. Open the shared contextual sidebar from any example;
primary swatches reflect the color you pick there. Error swatches show the active
preset's values; editing any knob switches to custom and resets error to TWW · Ink.
Binding
Token
background
color.surface
foreground
color.on-surface
muted
color.on-surface-variant
border-color
color.outline
composer-background
color.surface-container
action-background
color.primary
action-foreground
color.on-primary
radius
rounded.md
padding-x
spacing.md
padding-y
spacing.md
gap
spacing.sm
05
States
State
Source
Summary
open
property
Thread is open.
resolved
property
Thread is resolved.
replying
interaction
Reply composer has focus or content.
06
Accessibility
From the contract's accessibility obligations. Role list; Thread id and status identify the comment thread; hosts may wrap with a stronger label when needed.
Tab moves through resolve/reopen action, child comment actions, reply input, and submit button.
Enter or Space activates focused buttons.
The reply input accepts normal multiline text entry.
Web projections render thread comments as a native list of article-like comment items.
Resolve/reopen and reply submit controls are buttons named by component-owned localized messages.
Reply input is labelled by the component-owned reply message.
Empty reply submissions are ignored locally and do not dispatch reply-submit.