SearchCtrlK

  • ↑↓navigate
  • ↵open
  • escclose

Can’t find it? Contact us

ReviewThread

A conversation anchored to lines of a change.

  1. AS

    anacommentedyesterday

    The same > 0xffff ? 2 : 1 check shows up again on line 14. Worth a codePointLength() helper?

  2. SX

    sergiocommented2 hours ago

    Agreed, the string lexer needs it too. I'll add it to unicode.ts.

View code
tsx
<Frame name="thread-1841" src="/acme/delta/pull/412/threads/1841">
	<ReviewThread
		frame="thread-1841"
		comments={[
			{ id: "discussion_r1841", avatar: <Avatar>…</Avatar>, author: "ana", action: "commented", time: "yesterday", body: <p>…</p> },
			{ id: "discussion_r1847", avatar: <Avatar>…</Avatar>, author: "sergio", action: "commented", time: "2 hours ago", body: <p>…</p> },
		]}
		replyAction="/acme/delta/pull/412/threads/1841/replies"
		resolveAction="/acme/delta/pull/412/threads/1841/resolve"
		fields={<input type="hidden" name="csrf" value={token} />}
		labels={{
			reply: "Reply",
			replyPlaceholder: "Reply…",
			resolve: "Resolve conversation",
			unresolve: "Unresolve conversation",
			outdated: "Outdated",
			pending: "Pending",
			suggestion: "Suggested change",
			applySuggestion: "Apply suggestion",
			columns,
		}}
	/>
</Frame>

Demo apps

Whole pages built only from @sdxc/ui, each for an invented product with its own palette and type. They open in a new tab.

Every demo, landings and docs sites alike, is listed in the demo gallery.

Installation

An ordinary dependency: install it, import the theme once, and import the component where it is used.

Terminal
npm add @sdxc/ui remix
typescript
import "@sdxc/ui/theme.css";

Usage

typescript
import { ReviewThread } from "@sdxc/ui/app";

Open, it shows the excerpt, comments, suggestion and forms; resolved or outdated, it folds into a closed <details> behind one line. Every form is a plain POST, so it works without script and needs no island; with the page's runtime, frame reloads only the thread in place.

Examples

Resolved, folded to one line

sergio marked this conversation as resolved
  1. AS

    anacommentedyesterday

    Nothing imports ./chars.js after this. Can it go?

  2. SX

    sergiocommentedyesterday

    Deleted in b81d3e7.

View code
tsx
<ReviewThread
	resolved
	resolvedSummary="sergio marked this conversation as resolved"
	comments={comments}
	resolveAction="/acme/delta/pull/412/threads/1838/resolve"
	labels={labels}
/>

Outdated, with the code it was about

Outdatedsrc/lexer/identifier.ts
src/lexer/identifier.ts
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ import { isIdentifierPart, isIdentifierStart } from "./chars.js";
/** Reads one identifier starting at `offset`, or nothing when none starts there. */
export function readIdentifier(source: string, offset: number): Token | undefined {
if (!isIdentifierStart(source.charCodeAt(offset))) return undefined;
if (!isIdentifierStart(source.codePointAt(offset) ?? 0)) return undefined;
let end = offset + 1;
  1. MC

    meicommented3 days ago

    This reads 𝑥 as a start, but end = offset + 1 below still lands inside its surrogate pair.

View code
tsx
<ReviewThread
	outdated
	excerpt={{ path: "src/lexer/identifier.ts", hunks: excerpt, language: "ts" }}
	comments={comments}
	replyAction="/acme/delta/pull/412/threads/1822/replies"
	labels={labels}
/>

A suggested change and a pending reply

  1. AS

    anasuggested a changeyesterday

    Line 14 repeats this check. With a helper in unicode.ts both read the same:

  2. SX

    sergiocommentedPendingjust now

    Applying it, and the same on line 14.

Suggested change

Original file line numberDiff line numberDiff line change
@@ -10 +10 @@ export function readIdentifier(source: string, offset: number): Token | undefine
let end = offset + (first > 0xffff ? 2 : 1);
let end = offset + codePointLength(first);
View code
tsx
<ReviewThread
	comments={[
		{ author: "ana", action: "suggested a change", body: <p>…</p>, … },
		{ author: "sergio", action: "commented", pending: true, body: <p>…</p>, … },
	]}
	suggestion={{
		path: "src/lexer/identifier.ts",
		hunks: diffLines(before, after, { context: 0 }).hunks,
		language: "ts",
		applyAction: "/acme/delta/pull/412/threads/1841/suggestions/1/apply",
	}}
	replyAction="/acme/delta/pull/412/threads/1841/replies"
	resolveAction="/acme/delta/pull/412/threads/1841/resolve"
	labels={labels}
/>

Props

Read from the component's own types, so every prop, every default and every allowed value is listed.

PropTypeRequiredDescription
commentsreadonly Comment[]YesThe thread's comments, oldest first.
resolvedbooleanNoThe thread is resolved, which folds it to resolvedSummary.
resolvedSummaryRemixNodeNoThe folded line of a resolved thread, such as "ana marked this conversation as resolved".
outdatedbooleanNoThe lines the thread cites have changed since, which folds it and labels it.
excerptLinesNoThe code the thread is about, drawn above the comments: on the timeline, and for an outdated thread, whose original lines no longer show in the diff.
suggestionSuggestionNoA change the reviewer proposes, drawn as a small diff under the comments.
replyActionstringNoWhere a reply posts its text (as body); omitted, the thread draws no reply box.
resolveActionstringNoWhere Resolve and Unresolve post resolved=true|false; omitted, no button.
framestringNoThe named frame the thread renders in, which every form reloads in place.
fieldsRemixNodeNoHidden inputs every form carries, such as a CSRF token.
labelsLabelsYesEvery string the thread shows or announces, so it reads in any language.

Also accepts everything in Omit<TagProps<"div">, "children">.

@sdxc/ui

Can’t find it? Contact us