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
4 changes: 2 additions & 2 deletions app/components/console/console-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export default function ConsolePage() {
<ErrorMessage error={error} />
</section>
<p className="secondary-text">
The key file can open every response in its session. Keep it on this computer and in your study team&apos;s
Teams channel files only.
The key file can open every response in its session. Keep it on this computer and in one secure place your
study has approved for its files, and nowhere else.
</p>
</main>
</Frame>
Expand Down
12 changes: 6 additions & 6 deletions app/components/console/publish-steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useUnsavedGuard } from "./use-unsaved-guard";
// Save the key file (twice), then publish. Publishing is safe to retry: the request is rebuilt from the file.
export default function PublishSteps({ file, onPublished }: { file: SessionFile; onPublished: () => void }) {
const [saved, setSaved] = useState(false);
const [teamsCopy, setTeamsCopy] = useState(false);
const [secondCopy, setSecondCopy] = useState(false);
const [creationKey, setCreationKey] = useState(() => readKey(CREATION_KEY_STORAGE) ?? "");
const [remember, setRemember] = useState(true);
const [busy, setBusy] = useState(false);
Expand Down Expand Up @@ -71,9 +71,9 @@ export default function PublishSteps({ file, onPublished }: { file: SessionFile;
</Button>
</div>
<Checkbox
checked={teamsCopy}
onChange={(e) => setTeamsCopy(e.target.checked)}
label="I also saved a copy in our study team's Teams channel files."
checked={secondCopy}
onChange={(e) => setSecondCopy(e.target.checked)}
label="I also saved a second copy in a secure place our study has approved for its files."
/>
</section>
<form className="panel stack" onSubmit={publish}>
Expand All @@ -94,11 +94,11 @@ export default function PublishSteps({ file, onPublished }: { file: SessionFile;
/>
<ErrorMessage error={error} />
<div>
<Button primary type="submit" disabled={busy || !saved || !teamsCopy || !creationKey.trim()}>
<Button primary type="submit" disabled={busy || !saved || !secondCopy || !creationKey.trim()}>
{busy ? "Publishing…" : "Publish the session"}
</Button>
</div>
{(!saved || !teamsCopy) && <p className="secondary-text">Save the key file and its second copy first.</p>}
{(!saved || !secondCopy) && <p className="secondary-text">Save the key file and its second copy first.</p>}
</form>
</main>
</Frame>
Expand Down
2 changes: 1 addition & 1 deletion app/components/console/session-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default function SessionView({
<div role="status" className="console-unsaved">
<Banner mode="warning" withIcon>
<span className="console-unsaved__body">
The participant list changed. Save the updated key file, and replace the copy in Teams.
The participant list changed. Save the updated key file, and replace your second copy with it.
<Button primary onClick={saveFile}>
{withIcon(downloadIcon, "Save key file")}
</Button>
Expand Down
4 changes: 0 additions & 4 deletions app/components/participant/sort-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { Content, Header } from "@jetbrains/ring-ui-built/components/island/isla
import ProgressBar from "@jetbrains/ring-ui-built/components/progress-bar/progress-bar";
import Text from "@jetbrains/ring-ui-built/components/text/text";
import { ConfirmDialog, ErrorMessage } from "@malverdesoftware/ui/controls";
import { useBoardTools } from "@/lib/board-tools";
import { checkResponse, orderCards, type SortDefinition } from "@/lib/session";
import type { ResponseBody } from "@/lib/model";
import SortBoard, { count, type BoardView } from "./sort-board";
Expand Down Expand Up @@ -43,9 +42,6 @@ export default function SortWorkspace({ session, definition, initial, seed, onSu
const { state, saveNow } = useAutosave(session, body, !busy && !closed);
const editable = !busy && !closed && state !== "closed" && state !== "conflict";
const cards = useMemo(() => orderCards(definition.cards, definition.randomize, seed), [definition, seed]);
const move = (card: string, pile: string | null) =>
setBody((b) => ({ ...b, assignments: { ...b.assignments, [card]: pile } }));
useBoardTools(definition.cards, body, editable, move);
const assigned = cards.filter((c) => body.assignments[c.id]).length;

async function submit() {
Expand Down
112 changes: 0 additions & 112 deletions app/lib/board-tools.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/tests/console-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ try {
const savedKeyFile = await download(keyFile);
const file = parseSessionFile(readFileSync(savedKeyFile, "utf8"));
assert.deepEqual(file.participants.map((p) => p.label), ["Y01", "Y02"]);
await tap("label", "Teams channel files");
await tap("label", "saved a second copy");
await type('input[type="password"]', creationKey);
await screenshot("console-prepared");
await tap("button", "Publish the session");
Expand Down
15 changes: 8 additions & 7 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@

## Working in 0.1

- Text cards, CSV import, and three configurable workflows
- Separate links for each participant, with saved drafts and final submissions
- Text cards, typed in or imported from a CSV, and one activity: each participant sorts the same set of cards on their own
- A personal link and six-digit passcode for each participant, with saved drafts and one final submission
- Participant-defined piles, optional explanations, and unsorted cards
- Contribution review and a fixed shared set of cards, with where each card came from
- Responses sealed in the participant's browser, which only the researcher's key file can open; the site stores sealed data and deletes it when the session is deleted or expires
- A researcher console that runs on the researcher's own computer: create a session, follow progress, add or remove participants, collect, close and delete
- CSV and JSON exports, a co-sorting matrix, a circular connection map, and average-linkage clustering
- Local development, a standalone Cloudflare deployment setup, and Sites hosting
- Local development and a standalone Cloudflare deployment setup

## Next

- Pilot with synthetic data and a small group of community members and researchers
- Bring back the card-generation and collect-review-sort activities described in [methods](METHODS.md), with contribution review and a record of where each card came from
- Decide whether participants need to correct or withdraw a submission afterward
- Add saved review drafts for facilitators, access-key rotation and recovery, and study deletion
- Add key rotation and recovery
- Add optional verified invitation tokens and deployment-level abuse controls for large public use
- Try collaborative live sorting as its own mode, clearly separate from independent responses
- Try researcher-reviewed category crosswalks and optional multidimensional scaling, with documented stress diagnostics
- Try a multilingual interface, offline facilitation, images and audio, and more accessibility testing
- Mint a DOI at the first public release and add it to NOTICE, CITATION.cff and the README (the license, funding acknowledgment and `CITATION.cff` were added 2026-11-12)

None of these are in the current app.
None of the items under Next are in the current app.
Loading