Repair Board context reference replay

Co-authored-by: Larry <larry@buzz.local>
Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-08-20 02:09:59 +00:00
parent e490776b63
commit 7e44168431
8 changed files with 394 additions and 91 deletions

View File

@@ -5874,6 +5874,54 @@ mod tests {
assert!(json_to_context_message(&obj).is_none());
}
#[test]
fn test_json_to_context_message_projects_only_strict_board_references() {
let workstream_id = "123e4567-e89b-12d3-a456-426614174000";
let valid = json!({
"kind": "workstream",
"identity": workstream_id,
"snapshot": { "label": "Checkout recovery" },
"placement": {
"workstreamId": workstream_id,
"workstreamLabel": "checkout-recovery",
"sectionId": "workstream",
"sectionLabel": "Workstream"
}
});
let mut unknown = valid.clone();
unknown["snapshot"]["extra"] = json!(true);
let mut controlled = valid.clone();
controlled["snapshot"]["label"] = json!("bad\u{0085}label");
let mut forged = valid.clone();
forged["identity"] = json!("123e4567-e89b-12d3-a456-426614174001");
let tag = |value: serde_json::Value| {
json!([
buzz_sdk::BOARD_REFERENCE_TAG,
buzz_sdk::BOARD_REFERENCE_VERSION,
serde_json::to_string(&value).unwrap()
])
};
let obj = json!({
"pubkey": "abc",
"content": "hello",
"created_at": 1710518400,
"tags": [
["h", workstream_id],
tag(unknown),
tag(controlled),
tag(forged),
tag(valid.clone())
]
});
let msg = json_to_context_message(&obj).expect("message survives bad references");
assert_eq!(msg.board_references.len(), 1);
assert_eq!(
serde_json::to_value(&msg.board_references[0]).unwrap(),
valid
);
}
#[test]
fn test_collect_prompt_pubkeys_includes_authors_mentions_and_context() {
let keys = Keys::generate();

View File

@@ -106,13 +106,16 @@ pub enum BoardReference {
}
impl BoardReference {
fn dedup_key(&self) -> (&'static str, String) {
fn dedup_key(&self) -> (String, &'static str, String) {
let workstream_id = self.placement().workstream_id.clone();
match self {
Self::Workstream { identity, .. } => ("workstream", identity.clone()),
Self::PullRequest { identity, .. } => ("pull-request", identity.clone()),
Self::Agent { identity, .. } => ("agent", identity.clone()),
Self::AgentGroup { identity, .. } => ("agent-group", identity.clone()),
Self::ThreadBlocker { identity, .. } => ("thread-blocker", identity.clone()),
Self::Workstream { identity, .. } => (workstream_id, "workstream", identity.clone()),
Self::PullRequest { identity, .. } => (workstream_id, "pull-request", identity.clone()),
Self::Agent { identity, .. } => (workstream_id, "agent", identity.clone()),
Self::AgentGroup { identity, .. } => (workstream_id, "agent-group", identity.clone()),
Self::ThreadBlocker { identity, .. } => {
(workstream_id, "thread-blocker", identity.clone())
}
}
}
@@ -157,6 +160,7 @@ impl BoardReference {
&& valid_label(&snapshot.label)
&& snapshot.detail.as_deref().is_none_or(valid_label)
&& match self {
Self::Workstream { identity, .. } => identity == &placement.workstream_id,
Self::Agent { identity, .. } => valid_hex64(identity),
Self::ThreadBlocker { destination, .. } => {
valid_uuid(&destination.channel_id)
@@ -263,15 +267,15 @@ mod tests {
use super::*;
use nostr::Tags;
fn reference(identity: &str) -> BoardReference {
fn reference(workstream_id: &str) -> BoardReference {
BoardReference::Workstream {
identity: identity.into(),
identity: workstream_id.into(),
snapshot: BoardSnapshot {
label: "Checkout recovery".into(),
detail: Some("Active".into()),
},
placement: BoardPlacement {
workstream_id: "123e4567-e89b-12d3-a456-426614174000".into(),
workstream_id: workstream_id.into(),
workstream_label: "checkout-recovery".into(),
section_id: "workstream".into(),
section_label: "Workstream".into(),
@@ -281,29 +285,102 @@ mod tests {
#[test]
fn round_trip_preserves_order() {
let tags = build_board_reference_tags(&[reference("one"), reference("two")]).unwrap();
let tags = build_board_reference_tags(&[
reference("123e4567-e89b-12d3-a456-426614174000"),
reference("123e4567-e89b-12d3-a456-426614174001"),
])
.unwrap();
let parsed = parse_board_references(&Tags::from_list(tags));
assert_eq!(parsed, vec![reference("one"), reference("two")]);
assert_eq!(
parsed,
vec![
reference("123e4567-e89b-12d3-a456-426614174000"),
reference("123e4567-e89b-12d3-a456-426614174001")
]
);
}
#[test]
fn malformed_unknown_and_duplicate_entries_fail_closed() {
let mut tags = build_board_reference_tags(&[reference("one")]).unwrap();
let mut tags =
build_board_reference_tags(&[reference("123e4567-e89b-12d3-a456-426614174000")])
.unwrap();
tags.push(Tag::parse([BOARD_REFERENCE_TAG, "99", "{}"]).unwrap());
tags.push(Tag::parse([BOARD_REFERENCE_TAG, BOARD_REFERENCE_VERSION, "not-json"]).unwrap());
tags.extend(build_board_reference_tags(&[reference("one"), reference("two")]).unwrap());
tags.extend(
build_board_reference_tags(&[
reference("123e4567-e89b-12d3-a456-426614174000"),
reference("123e4567-e89b-12d3-a456-426614174001"),
])
.unwrap(),
);
assert_eq!(
parse_board_references(&Tags::from_list(tags)),
vec![reference("one"), reference("two")]
vec![
reference("123e4567-e89b-12d3-a456-426614174000"),
reference("123e4567-e89b-12d3-a456-426614174001")
]
);
}
#[test]
fn cross_workstream_references_fail_closed() {
let allowed = reference("allowed");
let mut other = reference("other");
if let BoardReference::Workstream { placement, .. } = &mut other {
placement.workstream_id = "123e4567-e89b-12d3-a456-426614174001".into();
fn strict_unknown_controls_and_forged_workstream_binding_fail_closed() {
let valid = reference("123e4567-e89b-12d3-a456-426614174000");
let payload = serde_json::to_value(&valid).unwrap();
let mut invalid = Vec::new();
for path in ["top", "snapshot", "placement"] {
let mut value = payload.clone();
let target = match path {
"top" => value.as_object_mut().unwrap(),
"snapshot" => value.get_mut("snapshot").unwrap().as_object_mut().unwrap(),
_ => value.get_mut("placement").unwrap().as_object_mut().unwrap(),
};
target.insert("extra".into(), serde_json::json!(true));
invalid.push(value);
}
let mut controlled = payload.clone();
controlled["snapshot"]["label"] = serde_json::json!("bad\u{0085}label");
invalid.push(controlled);
let mut forged = payload;
forged["identity"] = serde_json::json!("123e4567-e89b-12d3-a456-426614174001");
invalid.push(forged);
let mut tags = invalid
.into_iter()
.map(|value| {
Tag::parse([
BOARD_REFERENCE_TAG,
BOARD_REFERENCE_VERSION,
serde_json::to_string(&value).unwrap().as_str(),
])
.unwrap()
})
.collect::<Vec<_>>();
tags.extend(build_board_reference_tags(&[valid.clone()]).unwrap());
assert_eq!(parse_board_references(&Tags::from_list(tags)), vec![valid]);
}
#[test]
fn dedup_key_is_scoped_to_workstream_placement() {
let first = reference("123e4567-e89b-12d3-a456-426614174000");
let mut second = first.clone();
if let BoardReference::Workstream {
identity,
placement,
..
} = &mut second
{
*identity = "123e4567-e89b-12d3-a456-426614174001".into();
placement.workstream_id = identity.clone();
}
let tags =
Tags::from_list(build_board_reference_tags(&[first.clone(), second.clone()]).unwrap());
assert_eq!(parse_board_references(&tags), vec![first, second]);
}
#[test]
fn cross_workstream_references_fail_closed() {
let allowed = reference("123e4567-e89b-12d3-a456-426614174000");
let other = reference("123e4567-e89b-12d3-a456-426614174001");
let tags = Tags::from_list(build_board_reference_tags(&[allowed.clone(), other]).unwrap());
assert_eq!(
parse_board_references_for_workstream(&tags, "123e4567-e89b-12d3-a456-426614174000"),

View File

@@ -20,7 +20,7 @@ const ref = (kind, identity, workstreamId = WS) => ({
},
});
test("ordered mixed references round trip while malformed and cross-workstream entries fail closed", () => {
const valid = [ref("workstream", "one"), ref("pull-request", "one")];
const valid = [ref("workstream", WS), ref("pull-request", "one")];
const tags = [
...boardReferenceTags(valid),
["buzz:board-ref", "99", "{}"],
@@ -31,9 +31,9 @@ test("ordered mixed references round trip while malformed and cross-workstream e
});
test("resolution distinguishes same identity across kinds and removed objects", () => {
const refs = [
ref("workstream", "same"),
ref("pull-request", "same"),
ref("agent-group", "gone"),
ref("agent-group", "same"),
ref("pull-request", "gone"),
];
const live = new Map([
[boardReferenceKey(refs[0]), refs[0]],
@@ -47,3 +47,42 @@ test("resolution distinguishes same identity across kinds and removed objects",
["live", "changed", "historical"],
);
});
test("strict decoding rejects unknown fields and Unicode controls per reference", () => {
const valid = ref("workstream", WS);
const malformed = [
{ ...valid, extra: true },
{ ...valid, snapshot: { ...valid.snapshot, extra: true } },
{ ...valid, placement: { ...valid.placement, extra: true } },
{ ...valid, snapshot: { label: "bad\u0085label" } },
];
const tags = malformed.map((value) => [
"buzz:board-ref",
"1",
JSON.stringify(value),
]);
assert.deepEqual(
parseBoardReferences([...tags, ...boardReferenceTags([valid])]),
[valid],
);
});
test("workstream identity is canonically bound to placement", () => {
const forged = ref("workstream", OTHER);
forged.placement.workstreamId = WS;
const tag = ["buzz:board-ref", "1", JSON.stringify(forged)];
assert.deepEqual(parseBoardReferences([tag], WS), []);
});
test("same reusable identity is scoped to its recorded workstream", () => {
const inA = ref("agent", "6".repeat(64), WS);
const inB = ref("agent", "6".repeat(64), OTHER);
const live = new Map([
[boardReferenceKey(inA), inA],
[boardReferenceKey(inB), inB],
]);
assert.notEqual(boardReferenceKey(inA), boardReferenceKey(inB));
assert.equal(resolveBoardReferences([inA], live)[0].state, "live");
live.delete(boardReferenceKey(inA));
assert.equal(resolveBoardReferences([inA], live)[0].state, "historical");
});

View File

@@ -37,33 +37,56 @@ const KINDS = new Set([
"agent-group",
"thread-blocker",
]);
const CONTROL_CHARACTER = /\p{Cc}/u;
const safe = (value: unknown, max = 160): value is string =>
typeof value === "string" &&
value.trim().length > 0 &&
value.length <= max &&
![...value].some((character) => {
const code = character.charCodeAt(0);
return code < 32 || code === 127;
});
!CONTROL_CHARACTER.test(value);
const hasExactKeys = (
value: Record<string, unknown>,
required: readonly string[],
optional: readonly string[] = [],
): boolean => {
const allowed = new Set([...required, ...optional]);
return (
required.every((key) => Object.hasOwn(value, key)) &&
Object.keys(value).every((key) => allowed.has(key))
);
};
export function isBoardReference(value: unknown): value is BoardReference {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const v = value as Record<string, unknown>;
if (typeof v.kind !== "string" || !KINDS.has(v.kind)) return false;
const topLevelKeys =
v.kind === "thread-blocker"
? ["kind", "identity", "destination", "snapshot", "placement"]
: ["kind", "identity", "snapshot", "placement"];
if (!hasExactKeys(v, topLevelKeys) || !safe(v.identity, 512)) return false;
if (
typeof v.kind !== "string" ||
!KINDS.has(v.kind) ||
!safe(v.identity, 512) ||
!v.snapshot ||
typeof v.snapshot !== "object" ||
Array.isArray(v.snapshot) ||
!safe((v.snapshot as Record<string, unknown>).label) ||
((v.snapshot as Record<string, unknown>).detail !== undefined &&
!safe((v.snapshot as Record<string, unknown>).detail))
Array.isArray(v.snapshot)
)
return false;
const snapshot = v.snapshot as Record<string, unknown>;
if (
!hasExactKeys(snapshot, ["label"], ["detail"]) ||
!safe(snapshot.label) ||
(snapshot.detail !== undefined && !safe(snapshot.detail))
)
return false;
const p = v.placement as Record<string, unknown> | undefined;
if (
!p ||
!hasExactKeys(p, [
"workstreamId",
"workstreamLabel",
"sectionId",
"sectionLabel",
]) ||
!safe(p.workstreamId, 36) ||
!UUID.test(p.workstreamId) ||
!safe(p.workstreamLabel) ||
@@ -71,11 +94,13 @@ export function isBoardReference(value: unknown): value is BoardReference {
!safe(p.sectionLabel)
)
return false;
if (v.kind === "workstream" && v.identity !== p.workstreamId) return false;
if (v.kind === "agent" && !HEX.test(v.identity)) return false;
if (v.kind === "thread-blocker") {
const d = v.destination as Record<string, unknown> | undefined;
if (
!d ||
!hasExactKeys(d, ["channelId", "messageId"], ["threadRootId"]) ||
!safe(d.channelId, 36) ||
!UUID.test(d.channelId) ||
!safe(d.messageId, 64) ||
@@ -162,7 +187,7 @@ export function resolveBoardReferences(
}
export function boardReferenceKey(
reference: Pick<BoardReference, "kind" | "identity">,
reference: Pick<BoardReference, "kind" | "identity" | "placement">,
): string {
return `${reference.kind}:${reference.identity}`;
return `${reference.placement.workstreamId}:${reference.kind}:${reference.identity}`;
}

View File

@@ -0,0 +1,37 @@
import type { BoardReference } from "@/features/workstream-board/lib/boardReferences";
import { boardReferenceKey } from "@/features/workstream-board/lib/boardReferences";
import { cn } from "@/shared/lib/cn";
export function BoardReferenceItems({
references,
onRemove,
}: {
references: readonly BoardReference[];
onRemove?: (reference: BoardReference) => void;
}) {
return (
<ol className="flex flex-wrap gap-1.5" data-testid="board-reference-items">
{references.map((reference, index) => (
<li
className="inline-flex items-center gap-1 rounded-full border bg-background px-2 py-1 text-3xs"
data-reference-key={boardReferenceKey(reference)}
key={boardReferenceKey(reference)}
>
<span>
{index + 1}. {reference.kind}: {reference.snapshot.label}
</span>
{onRemove ? (
<button
aria-label={`Remove ${reference.snapshot.label}`}
className={cn("font-semibold underline")}
onClick={() => onRemove(reference)}
type="button"
>
Remove
</button>
) : null}
</li>
))}
</ol>
);
}

View File

@@ -1,5 +1,6 @@
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { parseBoardReferences } from "@/features/workstream-board/lib/boardReferences";
import { BoardReferenceItems } from "@/features/workstream-board/ui/BoardReferenceItems";
export const BOARD_REPLAY_STORAGE_KEY = "buzz:board-replay:v1";
@@ -26,14 +27,7 @@ export function BoardReferenceSet({
}}
type="button"
>
{references.map((reference, index) => (
<span
className="rounded-full border bg-background px-2 py-1 text-3xs"
key={`${reference.kind}:${reference.identity}`}
>
{index + 1}. {reference.kind}: {reference.snapshot.label}
</span>
))}
<BoardReferenceItems references={references} />
</button>
);
}

View File

@@ -22,8 +22,10 @@ import {
} from "@/features/workstream-board/lib/workstreamWaits";
import { WorkstreamCard } from "@/features/workstream-board/ui/WorkstreamCard";
import { BOARD_REPLAY_STORAGE_KEY } from "@/features/workstream-board/ui/BoardReferenceSet";
import { BoardReferenceItems } from "@/features/workstream-board/ui/BoardReferenceItems";
import { useIdentityQuery } from "@/shared/api/hooks";
import { Button } from "@/shared/ui/button";
import { cn } from "@/shared/lib/cn";
import { PageHeader } from "@/shared/ui/PageHeader";
const WORKSTREAM_CARD_GRID_CLASS =
@@ -59,16 +61,26 @@ export function WorkstreamBoardScreen() {
BoardReference[]
>([]);
const [replayFocusIndex, setReplayFocusIndex] = React.useState(0);
const startReplay = React.useCallback((references: BoardReference[]) => {
setReplayFocusIndex(0);
setReplayReferences(references);
}, []);
React.useEffect(() => {
if (!discussionMode && replayReferences.length === 0) return;
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
if (replayReferences.length > 0) setReplayReferences([]);
else if (
selectedReferences.length === 0 ||
window.confirm("Discard the unsent reference tray?")
)
(selectedReferences.length === 0 && draft.length === 0) ||
window.confirm(
"Discard the unsent discussion draft and reference tray?",
)
) {
setDiscussionMode(false);
setSelectedReferences([]);
setDiscussionChannelId(null);
setDraft("");
}
} else if (
replayReferences.length > 0 &&
(event.key === "ArrowRight" || event.key === "]")
@@ -90,7 +102,7 @@ export function WorkstreamBoardScreen() {
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [discussionMode, replayReferences, selectedReferences.length]);
}, [discussionMode, draft, replayReferences, selectedReferences.length]);
const [liveReferencesByChannel, setLiveReferencesByChannel] = React.useState<
ReadonlyMap<string, readonly BoardReference[]>
>(() => new Map());
@@ -101,7 +113,7 @@ export function WorkstreamBoardScreen() {
try {
const parsed: unknown = JSON.parse(stored);
if (Array.isArray(parsed)) {
setReplayReferences(
startReplay(
parsed.flatMap((value) => {
const references = parseBoardReferences([
["buzz:board-ref", "1", JSON.stringify(value)],
@@ -113,7 +125,7 @@ export function WorkstreamBoardScreen() {
} catch {
// Malformed cross-route replay state fails closed.
}
}, []);
}, [startReplay]);
const workstreamChannels = filterWorkstreamChannels(channelsQuery.data ?? []);
const discussionChannel =
workstreamChannels.find((channel) => channel.id === discussionChannelId) ??
@@ -131,6 +143,9 @@ export function WorkstreamBoardScreen() {
() => new Set(replayReferences.map(boardReferenceKey)),
[replayReferences],
);
const replayFocusedKey = replayReferences[replayFocusIndex]
? boardReferenceKey(replayReferences[replayFocusIndex])
: undefined;
const liveReferences = React.useMemo(
() =>
new Map(
@@ -140,6 +155,21 @@ export function WorkstreamBoardScreen() {
),
[liveReferencesByChannel],
);
const resolvedReplayReferences = React.useMemo(
() => resolveBoardReferences(replayReferences, liveReferences),
[liveReferences, replayReferences],
);
const replayReferenceStates = React.useMemo(
() =>
new Map(
resolvedReplayReferences.flatMap((item) =>
item.state === "historical"
? []
: [[boardReferenceKey(item.reference), item.state] as const],
),
),
[resolvedReplayReferences],
);
const onReferencesChange = React.useCallback(
(channelId: string, references: readonly BoardReference[]) => {
setLiveReferencesByChannel((current) => {
@@ -242,14 +272,17 @@ export function WorkstreamBoardScreen() {
onClick={() => {
if (
discussionMode &&
selectedReferences.length > 0 &&
!window.confirm("Discard the unsent reference tray?")
(selectedReferences.length > 0 || draft.length > 0) &&
!window.confirm(
"Discard the unsent discussion draft and reference tray?",
)
)
return;
setDiscussionMode(!discussionMode);
setSelectedReferences([]);
setReplayReferences([]);
setDiscussionChannelId(null);
setDraft("");
}}
variant={discussionMode ? "secondary" : "default"}
>
@@ -270,25 +303,10 @@ export function WorkstreamBoardScreen() {
<p className="text-sm font-semibold">
Reference tray ({selectedReferences.length})
</p>
<ol className="space-y-1">
{selectedReferences.map((reference, index) => (
<li
className="flex items-center gap-2 text-xs"
key={boardReferenceKey(reference)}
>
<span>
{index + 1}. {reference.kind}: {reference.snapshot.label}
</span>
<button
className="underline"
onClick={() => toggleReference(reference)}
type="button"
>
Remove
</button>
</li>
))}
</ol>
<BoardReferenceItems
onRemove={toggleReference}
references={selectedReferences}
/>
<textarea
aria-label="Discussion message"
className="min-h-20 w-full rounded-md border bg-background p-2 text-sm"
@@ -329,15 +347,11 @@ export function WorkstreamBoardScreen() {
<button
className="block w-full rounded-md border bg-background p-2 text-left text-xs"
key={event.id}
onClick={() => setReplayReferences(refs)}
onClick={() => startReplay(refs)}
type="button"
>
<span className="block">{event.content}</span>
<span className="text-muted-foreground">
{refs
.map((reference) => reference.snapshot.label)
.join(" · ")}
</span>
<BoardReferenceItems references={refs} />
</button>
);
})}
@@ -349,9 +363,15 @@ export function WorkstreamBoardScreen() {
)}
{replayReferences.length > 0
? (() => {
const resolved = resolveBoardReferences(
replayReferences,
liveReferences,
const resolved = resolvedReplayReferences;
const fallbackHistorical = resolved.filter(
(item) =>
item.state === "historical" &&
!workstreamChannels.some(
(channel) =>
channel.id ===
item.reference.placement.workstreamId,
),
);
return (
<div className="text-xs">
@@ -403,20 +423,30 @@ export function WorkstreamBoardScreen() {
Next
</button>
</span>
{resolved.some(
(item) => item.state === "historical",
) ? (
{fallbackHistorical.length > 0 ? (
<div className="mt-2 rounded-md border border-dashed p-2">
<strong>Historical references</strong>
{resolved
.filter((item) => item.state === "historical")
.map((item) => (
<p key={boardReferenceKey(item.reference)}>
{fallbackHistorical.map((item) => {
const key = boardReferenceKey(item.reference);
const current = replayFocusedKey === key;
return (
<p
aria-current={current ? "true" : undefined}
className={cn(
"rounded px-1",
current &&
"font-bold outline outline-2 outline-offset-2",
)}
data-testid="historical-reference-placeholder"
key={key}
>
{item.reference.snapshot.label} · formerly{" "}
{item.reference.placement.workstreamLabel} /{" "}
{item.reference.placement.sectionLabel}
{current ? " · Current" : ""}
</p>
))}
);
})}
</div>
) : null}
</div>
@@ -457,6 +487,15 @@ export function WorkstreamBoardScreen() {
onReferencesChange(channel.id, references)
}
replayReferenceKeys={replayKeys}
replayFocusedReferenceKey={replayFocusedKey}
replayReferenceStates={replayReferenceStates}
historicalReplayReferences={resolvedReplayReferences
.filter(
(item) =>
item.state === "historical" &&
item.reference.placement.workstreamId === channel.id,
)
.map((item) => item.reference)}
selectedReferenceKeys={selectedKeys}
currentOwnerPubkey={identityQuery.data?.pubkey}
key={channel.id}

View File

@@ -40,6 +40,9 @@ type WorkstreamCardProps = {
discussionMode?: boolean;
selectedReferenceKeys?: ReadonlySet<string>;
replayReferenceKeys?: ReadonlySet<string>;
replayFocusedReferenceKey?: string;
replayReferenceStates?: ReadonlyMap<string, "live" | "changed">;
historicalReplayReferences?: readonly BoardReference[];
onToggleReference?: (reference: BoardReference) => void;
onReferencesChange?: (references: readonly BoardReference[]) => void;
};
@@ -55,6 +58,9 @@ export function WorkstreamCard({
onReferencesChange,
profiles,
replayReferenceKeys,
replayFocusedReferenceKey,
replayReferenceStates,
historicalReplayReferences = [],
selectedReferenceKeys,
}: WorkstreamCardProps) {
const canvasQuery = useCanvasQuery(channel.id);
@@ -178,14 +184,25 @@ export function WorkstreamCard({
onReferencesChange?.(discussionReferences);
}, [discussionReferences, onReferencesChange]);
const workstreamReference = discussionReferences[0];
const workstreamReferenceKey = workstreamReference
? boardReferenceKey(workstreamReference)
: undefined;
const workstreamIsCurrent =
workstreamReferenceKey !== undefined &&
replayFocusedReferenceKey === workstreamReferenceKey;
return (
<div
aria-current={workstreamIsCurrent ? "true" : undefined}
className={cn(
"group relative w-full self-start overflow-hidden rounded-2xl border border-border/70 bg-muted/50 p-5 text-left text-foreground shadow-xs transition-all hover:-translate-y-0.5 hover:border-border hover:bg-muted/65 hover:shadow-md",
waitingOnPrincipal && "border-t-4 border-t-amber-400/70",
discussionReferences.some((reference) =>
replayReferenceKeys?.has(boardReferenceKey(reference)),
) && "ring-2 ring-primary ring-offset-2 ring-offset-background",
) && "border-dashed border-2",
workstreamIsCurrent &&
"ring-2 ring-primary ring-offset-2 ring-offset-background",
)}
data-testid={`workstream-card-${channel.id}`}
>
@@ -193,7 +210,7 @@ export function WorkstreamCard({
className="absolute inset-0 z-0 rounded-2xl focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
onClick={() =>
discussionMode
? onToggleReference?.(discussionReferences[0])
? workstreamReference && onToggleReference?.(workstreamReference)
: onSelect(channel.id)
}
type="button"
@@ -210,7 +227,7 @@ export function WorkstreamCard({
className="pointer-events-auto flex max-w-[calc(100%-4rem)] items-center gap-1.5 text-xs font-semibold text-muted-foreground hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
onClick={() =>
discussionMode
? onToggleReference?.(discussionReferences[0])
? workstreamReference && onToggleReference?.(workstreamReference)
: onSelect(channel.id)
}
type="button"
@@ -227,22 +244,49 @@ export function WorkstreamCard({
const key = boardReferenceKey(reference);
const selected = selectedReferenceKeys?.has(key) ?? false;
const replayed = replayReferenceKeys?.has(key) ?? false;
const replayState = replayReferenceStates?.get(key);
const current =
reference.kind !== "workstream" &&
replayFocusedReferenceKey === key;
return (
<button
aria-pressed={selected}
className={cn(
"rounded-full border px-2 py-1 text-3xs",
selected && "border-primary bg-primary/15",
replayed && "ring-2 ring-primary",
replayed && "border-dashed font-semibold",
current && "ring-2 ring-primary ring-offset-2",
)}
aria-current={current ? "true" : undefined}
key={key}
onClick={() => onToggleReference?.(reference)}
type="button"
>
{reference.kind}: {reference.snapshot.label}
{replayed ? ` · Referenced · ${replayState}` : ""}
{current ? " · Current" : ""}
</button>
);
})}
{historicalReplayReferences.map((reference) => {
const key = boardReferenceKey(reference);
const current = replayFocusedReferenceKey === key;
return (
<div
aria-current={current ? "true" : undefined}
className={cn(
"rounded-md border border-dashed px-2 py-1 text-3xs font-semibold",
current && "ring-2 ring-primary ring-offset-2",
)}
data-testid="historical-reference-placeholder"
key={key}
>
Historical {reference.placement.sectionLabel}:{" "}
{reference.snapshot.label}
{current ? " · Current" : ""}
</div>
);
})}
<button
className="rounded-full border px-2 py-1 text-3xs font-semibold"
onClick={() => onSelect(channel.id)}