refactor: simplify journal message types and implement GM rolls
Refactor journal message types to use flatter, more intuitive names: - `narrative` becomes `chat` - `article.reveal` becomes `reveal` - `roll.request`/`roll.result` becomes a single `roll` type Implement client-side dice roll resolution for GMs. When a GM uses the `/roll` command, the result is now calculated locally using `resolveRollPayload` before being sent to the stream, rather than requesting a roll from the server.
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
* JournalInput — chat-style textarea with command dispatch and autocomplete.
|
||||
*
|
||||
* - Enter sends, Shift+Enter inserts newline
|
||||
* - Plain text → "narrative" type
|
||||
* - `/roll 3d6kh1` → "roll.request" type
|
||||
* - `/link path#section` → "article.reveal" type
|
||||
* - Plain text → "chat" type
|
||||
* - `/roll 3d6kh1` → "roll" type (result resolved client-side by GM)
|
||||
* - `/link path#section` → "link" type
|
||||
* - `/` alone opens completions dropdown (populated from /__COMPLETIONS.json
|
||||
* in CLI mode, or client-side scan of the in-memory file index in dev mode)
|
||||
*/
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "solid-js";
|
||||
import { sendMessage, useJournalStream } from "../stores/journalStream";
|
||||
import { useJournalCompletions, ensureCompletions } from "./completions";
|
||||
import { resolveRollPayload } from "./types/roll";
|
||||
|
||||
// ---- Helpers ----
|
||||
|
||||
@@ -32,7 +33,7 @@ interface CompletionItem {
|
||||
}
|
||||
|
||||
interface ParsedInput {
|
||||
type: "narrative" | "roll.request" | "article.reveal";
|
||||
type: "chat" | "roll" | "link";
|
||||
payload: Record<string, unknown>;
|
||||
error?: string;
|
||||
}
|
||||
@@ -41,31 +42,26 @@ function parseInput(raw: string): ParsedInput {
|
||||
if (raw.startsWith("/roll ")) {
|
||||
const notation = raw.slice("/roll ".length).trim();
|
||||
if (!notation)
|
||||
return {
|
||||
type: "roll.request",
|
||||
payload: {},
|
||||
error: "Dice notation required",
|
||||
};
|
||||
return { type: "roll.request", payload: { notation, label: notation } };
|
||||
return { type: "roll", payload: {}, error: "Dice notation required" };
|
||||
return { type: "roll", payload: { notation, label: notation } };
|
||||
}
|
||||
|
||||
if (raw.startsWith("/link ")) {
|
||||
const arg = raw.slice("/link ".length).trim();
|
||||
if (!arg)
|
||||
return { type: "article.reveal", payload: {}, error: "Path required" };
|
||||
if (!arg) return { type: "link", payload: {}, error: "Path required" };
|
||||
const hashIdx = arg.indexOf("#");
|
||||
const path = hashIdx === -1 ? arg : arg.slice(0, hashIdx);
|
||||
const section =
|
||||
hashIdx === -1 ? undefined : arg.slice(hashIdx + 1) || undefined;
|
||||
return { type: "article.reveal", payload: { path, section } };
|
||||
return { type: "link", payload: { path, section } };
|
||||
}
|
||||
|
||||
// /roll or /link with no space — need to complete, don't send
|
||||
if (raw === "/roll" || raw === "/link") {
|
||||
return { type: "narrative", payload: {}, error: "Complete the command" };
|
||||
return { type: "chat", payload: {}, error: "Complete the command" };
|
||||
}
|
||||
|
||||
return { type: "narrative", payload: { text: raw } };
|
||||
return { type: "chat", payload: { text: raw } };
|
||||
}
|
||||
|
||||
// ---- Component ----
|
||||
@@ -98,9 +94,9 @@ export const JournalInput: Component = () => {
|
||||
const raw = text().trim();
|
||||
if (!raw) return;
|
||||
|
||||
// Players can't use commands — everything is narrative
|
||||
// Players / observers: everything is plain chat
|
||||
if (isPlayer() || isObserver()) {
|
||||
const result = sendMessage("narrative", { text: raw });
|
||||
const result = sendMessage("chat", { text: raw });
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
} else {
|
||||
@@ -119,6 +115,22 @@ export const JournalInput: Component = () => {
|
||||
setError(null);
|
||||
setSending(true);
|
||||
|
||||
// GM roll: resolve the dice result locally
|
||||
if (parsed.type === "roll") {
|
||||
const p = resolveRollPayload(
|
||||
parsed.payload as { notation: string; label?: string },
|
||||
);
|
||||
const result = sendMessage("roll", p);
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
setSending(false);
|
||||
textareaRef?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = sendMessage(parsed.type, parsed.payload);
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
|
||||
Reference in New Issue
Block a user