Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions apps/web/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,17 +961,34 @@ export const wording = {
},
/** The coordinator's questions as a card the person answers in the conversation. */
questions: {
title: "A few questions before going on",
/** The card's heading by how many questions it asks; three or more read "a few". */
title: (count: number) =>
({ 1: "A question for you", 2: "A couple of questions for you" })[
count
] ?? "A few questions for you",
other: "Other",
otherAnswer: "Your own answer",
/** The last step tab, where every answer is shown before it is sent. */
submit: "Submit",
steps: "Questions",
/** The keyboard hint under each step, by the kind of step. */
keys: {
single: "↑↓ move · Enter choose · ← back · → next",
multi: "↑↓ move · Space tick · Enter next · ← back · → next",
review: "Enter send · ← back",
},
composerPlaceholder: "Type here to respond to the above question",
/** The Settings switch between one question at a time and all at once. */
layoutSetting: "Ask questions one at a time",
layoutHint:
"On, each question is its own step you answer from the keyboard. Off, every question shows at once.",
send: "Send answers",
sending: "Sending…",
notSent: "Your answers were not sent. Your choices are kept; try again.",
/** The settled card's one line: each question's answer, in order. */
answered: (answers: readonly string[]) =>
`Answered: ${answers.join(" · ")}`,
answeredInChat: "Answered in the conversation.",
replaced: "Replaced by the questions that followed.",
/** The settled card's heading, over each question and its answer. */
answered: "Answered",
answeredInChat: "Answered in the conversation",
replaced: "Replaced by the questions that followed",
/** One list item of the person's reply per question: "- Who signs in? — Staff, Guests". */
answerLine: (prompt: string, answers: readonly string[]) =>
`- ${prompt} — ${answers.join(", ")}`,
Expand Down
17 changes: 16 additions & 1 deletion apps/web/src/pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
useDocumentTips,
writeDocumentTips,
} from "../document-tips-setting.ts";
import {
useQuestionsLayout,
writeQuestionsLayout,
} from "../questions-layout-setting.ts";
import {
addCustomReviewerPersona,
addWorkspaceReviewer,
Expand Down Expand Up @@ -438,10 +442,11 @@ export function WorkspaceApprovals() {
);
}

/** The theme, and the document tips choice kept in this browser. */
/** The theme, and the document tips and questions layout choices kept in this browser. */
function Appearance() {
const { mode, setMode } = useTheme();
const tips = useDocumentTips();
const questionsLayout = useQuestionsLayout();
const copy = wording.settings.appearance;
return (
<Section title={copy.section} description={copy.lead}>
Expand All @@ -466,6 +471,16 @@ function Appearance() {
onCheckedChange={(on) => writeDocumentTips(on ? "on" : "off")}
/>
</Row>
<Row
label={wording.questions.layoutSetting}
hint={wording.questions.layoutHint}
>
<Switch
label={wording.questions.layoutSetting}
checked={questionsLayout === "steps"}
onCheckedChange={(on) => writeQuestionsLayout(on ? "steps" : "all")}
/>
</Row>
</div>
</Section>
);
Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/pages/workspace/composer-placeholder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ function standing(input: {

function placeholder(input: {
approvalOpen?: boolean;
questionsOpen?: boolean;
chatFirst?: boolean;
status?: ProjectStatus;
}): string {
return composerPlaceholder({
approvalOpen: input.approvalOpen ?? false,
questionsOpen: input.questionsOpen ?? false,
chatFirst: input.chatFirst ?? false,
status: input.status,
});
Expand Down Expand Up @@ -154,4 +156,14 @@ describe("composer placeholder", () => {
}),
).toBe(wording.approval.composerPlaceholder);
});

test("open questions point at the card, after an open approval", () => {
const status = standing({ working: [{ role: "coder", state: "working" }] });
expect(placeholder({ questionsOpen: true, status })).toBe(
wording.questions.composerPlaceholder,
);
expect(
placeholder({ approvalOpen: true, questionsOpen: true, status }),
).toBe(wording.approval.composerPlaceholder);
});
});
3 changes: 3 additions & 0 deletions apps/web/src/pages/workspace/composer-placeholder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,15 @@ function passAlongRole(

export function composerPlaceholder(input: {
approvalOpen: boolean;
/** The coordinator's questions wait on the person; a message sent instead answers them in its own words. */
questionsOpen: boolean;
/** Absent until project status has loaded. */
status: ProjectStatus | undefined;
/** No pane deliverables, design draft, or concepts yet. */
chatFirst: boolean;
}): string {
if (input.approvalOpen) return wording.approval.composerPlaceholder;
if (input.questionsOpen) return wording.questions.composerPlaceholder;
const status = input.status;
if (status === undefined) return copy.reply;
const role = passAlongRole(status.working);
Expand Down
12 changes: 11 additions & 1 deletion apps/web/src/pages/workspace/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ import {
import type { ChatMessage, Wait } from "../../api/types.ts";
import { keys } from "../../queries/keys.ts";
import { COMPOSER_BOX_CLASS, CONV_CLASS, PANES_CLASS } from "./pane-classes.ts";
import { RoomThread, turnItemId, type TranscriptTurn } from "./transcript.tsx";
import {
questionsOpen,
RoomThread,
turnItemId,
type TranscriptTurn,
} from "./transcript.tsx";
import { cardDocuments } from "./collaboration.ts";
import { useFiledVersions } from "./filed-versions.tsx";
import { threadUpdates } from "./specialist-updates.tsx";
Expand Down Expand Up @@ -124,6 +129,7 @@ import {
DESIGN_OPTION,
} from "./design-concepts.tsx";
import { useLiveAppPresence } from "./live-app.tsx";
import { useQuestionsLayout } from "../../questions-layout-setting.ts";
import { NextStepLine, useProjectNextStep } from "./next-step.tsx";
import { composerPlaceholder } from "./composer-placeholder.ts";
import { Dictated } from "../../dictation.jsx";
Expand Down Expand Up @@ -348,6 +354,7 @@ export function StageWorkspace({
.map(paneArtifact);
const documents = snapshot?.documents ?? {};
const [liveRun, setLiveRun] = useState<LiveRun>(emptyLiveRun);
const questionsLayout = useQuestionsLayout();
const [approvalBusy, setApprovalBusy] = useState(false);
const [approvalError, setApprovalError] = useState<string | null>(null);
const [changesFor, setChangesFor] = useState<string | null>(null);
Expand Down Expand Up @@ -1422,6 +1429,8 @@ export function StageWorkspace({
ref={composerRef}
placeholder={composerPlaceholder({
approvalOpen: openApproval !== undefined,
questionsOpen:
!archived && questionsOpen(shownTurns, liveRun.results),
status: projectStatus.data,
chatFirst,
})}
Expand Down Expand Up @@ -1541,6 +1550,7 @@ export function StageWorkspace({
{...(sentId === undefined ? {} : { sentId })}
onAnswerQuestions={(reply) => deliver(reply)}
questionsDisabled={sending || archived}
questionsLayout={questionsLayout}
>
{archived ? (
<ConversationEmptyState
Expand Down
136 changes: 131 additions & 5 deletions apps/web/src/pages/workspace/questions-card.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,113 @@
min-width: 0;
}

/* GateBlock's "Needs you · form" label: the heading and the step tabs say what the card is. */
.questions-card > p:first-child {
display: none;
}

.questions-card > h3 {
margin-top: 0;
}

.questions-card-steps {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
margin-bottom: var(--space-3);
}

.questions-card-step {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 2px var(--space-2);
border: 1px solid transparent;
border-radius: 999px;
font-size: 0.75rem;
color: var(--muted-foreground);
}

.questions-card-step[aria-selected="true"] {
border-color: var(--primary-emphasis);
color: var(--foreground);
}

.questions-card-step:disabled {
opacity: 0.5;
}

.questions-card-step-mark {
width: 0.75rem;
height: 0.75rem;
color: var(--ok);
}

.questions-card-prompt {
margin: 0;
font-size: 0.875rem;
font-weight: 500;
}

.questions-card-options {
display: flex;
flex-direction: column;
gap: 2px;
border-radius: var(--radius-sm);
outline: none;
}

.questions-card-choice {
display: flex;
align-items: center;
gap: var(--space-2);
min-height: 2rem;
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
font-size: 0.875rem;
cursor: pointer;
}

.questions-card-options:focus-visible .questions-card-choice[data-active],
.questions-card-choice:hover {
background: color-mix(in oklab, var(--primary-emphasis) 12%, transparent);
}

.questions-card-choice-mark {
flex: none;
width: 1rem;
height: 1rem;
color: var(--primary-emphasis);
}

.questions-card-choice .questions-card-other-input {
flex: 1 1 auto;
min-width: 0;
}

.questions-card-answers {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin: 0 0 var(--space-3);
font-size: 0.875rem;
}

.questions-card-answers dt {
color: var(--muted-foreground);
}

.questions-card-answers dd {
margin: 0;
overflow-wrap: anywhere;
}

.questions-card-keys {
margin: var(--space-3) 0 0;
font-size: 0.6875rem;
color: var(--muted-foreground);
}

.questions-card-fields {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -43,16 +150,35 @@
min-width: 0;
}

/* The open card's frame, quiet: done, not waiting on anyone. */
.questions-card-settled {
justify-content: flex-start;
text-align: start;
max-width: 100%;
min-width: 0;
padding: 0.75rem 0.875rem;
border: 1px solid var(--border);
border-radius: var(--radius-lg, 0.5rem);
}

.questions-card-settled > span:last-child {
min-width: 0;
overflow-wrap: anywhere;
.questions-card-settled-title {
display: flex;
align-items: center;
gap: var(--space-2);
margin: 0;
font-size: 0.875rem;
font-weight: 500;
color: var(--muted-foreground);
}

.questions-card-settled-mark {
flex: none;
width: 1rem;
height: 1rem;
}

.questions-card-settled-ok {
color: var(--ok);
}

.questions-card-settled .questions-card-answers {
margin: var(--space-3) 0 0;
}
Loading