feat: publish initial manifest on startup and improve UI focus

- Publish the existing session manifest via the broker on startup
- Automatically focus the new session input field when it is shown
- Prevent event propagation when clicking to create a new session
This commit is contained in:
hypercross 2026-07-08 00:08:12 +08:00
parent 3eba8b3279
commit 57ffb35f2b
2 changed files with 35 additions and 3 deletions

View File

@ -150,6 +150,20 @@ export async function createJournalServer(
() => {}, () => {},
); );
// Publish the initial manifest on startup so clients see existing sessions
const initialManifest = loadManifest(root);
broker.publish(
{
topic: $SESSIONS,
payload: Buffer.from(JSON.stringify(initialManifest)),
qos: 1,
retain: true,
cmd: "publish" as const,
dup: false,
},
() => {},
);
console.log("[journal] persistence listener active"); console.log("[journal] persistence listener active");
console.log("[journal] broker available at ws://<host>:<port>"); console.log("[journal] broker available at ws://<host>:<port>");

View File

@ -5,7 +5,14 @@
* Auto-connects to ws://{current host}:{current port} — no URL input needed. * Auto-connects to ws://{current host}:{current port} — no URL input needed.
*/ */
import { Component, Show, createSignal, onMount, For } from "solid-js"; import {
Component,
Show,
createSignal,
onMount,
For,
createEffect,
} from "solid-js";
import { import {
connectStream, connectStream,
hydrateFromServer, hydrateFromServer,
@ -113,6 +120,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
const [newSessionName, setNewSessionName] = createSignal(""); const [newSessionName, setNewSessionName] = createSignal("");
const [showNewInput, setShowNewInput] = createSignal(false); const [showNewInput, setShowNewInput] = createSignal(false);
let dropdownRef!: HTMLDivElement; let dropdownRef!: HTMLDivElement;
let newSessionInputRef!: HTMLInputElement;
const handleSessionSelect = async (id: string) => { const handleSessionSelect = async (id: string) => {
try { try {
@ -132,6 +140,13 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
setShowNewInput(false); setShowNewInput(false);
}; };
// Focus the input when it appears
createEffect(() => {
if (showNewInput() && newSessionInputRef) {
newSessionInputRef.focus();
}
});
const handleDisconnect = () => disconnectStream(); const handleDisconnect = () => disconnectStream();
// Close dropdown on outside click // Close dropdown on outside click
@ -214,6 +229,7 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
fallback={ fallback={
<div class="px-3 py-1 flex gap-1"> <div class="px-3 py-1 flex gap-1">
<input <input
ref={newSessionInputRef}
type="text" type="text"
value={newSessionName()} value={newSessionName()}
onInput={(e) => onInput={(e) =>
@ -221,7 +237,6 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
} }
placeholder="会话名称" placeholder="会话名称"
class="flex-1 border border-gray-300 rounded px-1.5 py-0.5 text-xs" class="flex-1 border border-gray-300 rounded px-1.5 py-0.5 text-xs"
autofocus
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter") handleCreate(); if (e.key === "Enter") handleCreate();
if (e.key === "Escape") setShowNewInput(false); if (e.key === "Escape") setShowNewInput(false);
@ -237,7 +252,10 @@ const JournalHeader: Component<JournalHeaderProps> = (props) => {
} }
> >
<button <button
onClick={() => setShowNewInput(true)} onClick={(e) => {
e.stopPropagation();
setShowNewInput(true);
}}
class="w-full text-left px-3 py-1 text-xs text-blue-600 hover:bg-gray-100" class="w-full text-left px-3 py-1 text-xs text-blue-600 hover:bg-gray-100"
> >
+ +