From 7f97ca8dcad0d4a4855d8eed94f36e459b29f602 Mon Sep 17 00:00:00 2001 From: Sensemaker Date: Fri, 22 May 2026 08:42:08 +0000 Subject: [PATCH] site: add answer composer scaffolding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- README.md | 18 +++++++++++++++ src/main.tsx | 57 +++++++++++++++++++++++++++++++++++++++++++++-- src/styles.css | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 678053b..daa7210 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,24 @@ bun run build The static build lands in `dist/`. + +## Writing answers and survey direction + +Interviewer is becoming more than a renderer. The intended write model is: + +- **Closed interview:** one intended subject signs in and answers questions for a session. +- **Open survey:** a session can accept answers from many signed-in respondents. + +The current static app does **not** ask for app passwords. That is deliberate. Browser writing should use ATProto OAuth (authorization code flow, PKCE, DPoP, and public client metadata). Until that is wired, unanswered questions show a safe answer-record payload and an XRPC `createRecord` template so protocol users can still answer without putting credentials into the page. + +Future work: + +- publish OAuth client metadata for Interviewer +- add sign-in and session state +- write `network.sensemaker.answer` records to the respondent's PDS +- extend session semantics with `mode: closed | open` / `status: open | closed` +- index open-survey answers across many repos + ## Header cards Generate a 1200×628 PNG card: diff --git a/src/main.tsx b/src/main.tsx index 391fbda..10e5c98 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -9,6 +9,8 @@ type SessionRecord = { interviewer: string topic?: string introduction?: string + mode?: "closed" | "open" + status?: "open" | "closed" createdAt: string } @@ -151,6 +153,45 @@ function atUriToBsky(uri: string): string | null { } } +function createAnswerRecord(questionUri: string, text: string): AnswerRecord { + return { + $type: "network.sensemaker.answer", + question: questionUri, + text, + createdAt: new Date().toISOString(), + } +} + +function AnswerComposer({ questionUri }: { questionUri: string }) { + const [text, setText] = useState("") + const answerRecord = createAnswerRecord(questionUri, text) + const record = JSON.stringify(answerRecord, null, 2) + const body = JSON.stringify({ repo: "YOUR_DID", collection: COLLECTIONS.answer, record: answerRecord }, null, 2) + const curl = `curl -X POST "https://YOUR_PDS/xrpc/com.atproto.repo.createRecord" \\ + -H "Authorization: Bearer YOUR_ACCESS_JWT" \\ + -H "Content-Type: application/json" \\ + -d '${body.replace(/'/g, "'\\''")}'` + + async function copy(value: string) { + await navigator.clipboard?.writeText(value) + } + + return ( +
+

Answer this question

+