Skip to content

Pr/chat persistence ids stream finalize#1

Merged
aibozo merged 2 commits intomainfrom
pr/chat-persistence-ids-stream-finalize
Sep 5, 2025
Merged

Pr/chat persistence ids stream finalize#1
aibozo merged 2 commits intomainfrom
pr/chat-persistence-ids-stream-finalize

Conversation

@aibozo
Copy link
Copy Markdown
Owner

@aibozo aibozo commented Sep 5, 2025

Summary by cubic

Make chat streaming persistent and race-safe by moving assistant message creation to the server, introducing server-generated message IDs, and finalizing on stream completion. Adds a lightweight web app and tooling to exercise SSE chat end to end.

  • New Features

    • Server now owns assistant message IDs and emits assistant_started; stream finalizes on done.
    • Per-session append locks prevent RMW races; CORS enabled for the web client.
    • Exported chat append helper for realtime; added multi-turn persistence and SSE utilities.
    • New Next.js web app (Chat, Agents, Research, Settings) with SSE chat, code blocks, theming, and health badge.
    • run-web.sh to start core + web, with options for restart, release, and mock streaming.
  • Bug Fixes

    • Stop overwriting prior assistant turns: streaming tokens coalesce into one assistant message; a new message starts on role change.
    • Persist multi-turn chat reliably; tests cover assistant multi-turn streams and realtime coalescing.
    • Client treats server history as source of truth and refetches/merges on done to avoid local divergence.

…n locks\n\nfix(web/chat): stream by assistant id, single-source history, refetch-and-merge on done; stop overwriting prior turns\n\ntest(chat): add mock stream and multi-turn persistence test\n\nchore(scripts): run-web supports --mock-chat
…hange; include id\n\ntest(realtime): validate coalescing and multi-turn persistence
@aibozo aibozo merged commit fd7c08a into main Sep 5, 2025
1 check failed
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

39 issues found across 43 files

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Comment thread web/styles/globals.css
* { box-sizing: border-box; }

.focus-ring {
@apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--accent-500)] focus:ring-offset-[color:var(--bg)];
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid Tailwind utilities in @apply: ring-[color:…] and ring-offset-[color:…] are not recognized and can fail @apply. Use a valid ring color utility or an arbitrary value without the 'color:' prefix.

Prompt for AI agents
Address the following comment on web/styles/globals.css at line 43:

<comment>Invalid Tailwind utilities in @apply: ring-[color:…] and ring-offset-[color:…] are not recognized and can fail @apply. Use a valid ring color utility or an arbitrary value without the &#39;color:&#39; prefix.</comment>

<file context>
@@ -0,0 +1,56 @@
+* { box-sizing: border-box; }
+
+.focus-ring {
+  @apply focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[color:var(--accent-500)] focus:ring-offset-[color:var(--bg)];
+}
+
</file context>
Fix with Cubic

Comment thread web/app/settings/page.tsx

function Switch({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
return (
<button
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify button type to avoid unintended form submissions if this component is ever used inside a form.

Prompt for AI agents
Address the following comment on web/app/settings/page.tsx at line 69:

<comment>Specify button type to avoid unintended form submissions if this component is ever used inside a form.</comment>

<file context>
@@ -0,0 +1,79 @@
+
+function Switch({ checked, onChange }: { checked: boolean; onChange: (v: boolean) =&gt; void }) {
+  return (
+    &lt;button
+      role=&quot;switch&quot;
+      aria-checked={checked}
</file context>
Fix with Cubic

Comment thread web/tailwind.config.ts

export default {
darkMode: ['class', '[data-theme="dark"]'],
content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind content paths exclude web/lib where classes are used, which can purge required styles (e.g., Toaster). Include lib in content globs.

Prompt for AI agents
Address the following comment on web/tailwind.config.ts at line 5:

<comment>Tailwind content paths exclude web/lib where classes are used, which can purge required styles (e.g., Toaster). Include lib in content globs.</comment>

<file context>
@@ -0,0 +1,41 @@
+
+export default {
+  darkMode: [&#39;class&#39;, &#39;[data-theme=&quot;dark&quot;]&#39;],
+  content: [&#39;./app/**/*.{ts,tsx}&#39;, &#39;./components/**/*.{ts,tsx}&#39;],
+  theme: {
+    extend: {
</file context>
Fix with Cubic

Comment thread scripts/run-web.sh
--core-release) CORE_RELEASE=1; shift ;;
--mock-chat) MOCK_CHAT=1; shift ;;
-h|--help)
echo "Usage: $0 [--restart-core|-r] [--core-release]"; exit 0 ;;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Help usage message omits the --mock-chat flag that the script supports, which may confuse users.

Prompt for AI agents
Address the following comment on scripts/run-web.sh at line 20:

<comment>Help usage message omits the --mock-chat flag that the script supports, which may confuse users.</comment>

<file context>
@@ -0,0 +1,95 @@
+    --core-release) CORE_RELEASE=1; shift ;;
+    --mock-chat) MOCK_CHAT=1; shift ;;
+    -h|--help)
+      echo &quot;Usage: $0 [--restart-core|-r] [--core-release]&quot;; exit 0 ;;
+    *) echo &quot;[hub-web] Unknown option: $1&quot;; shift ;;
+  esac
</file context>
Fix with Cubic

Comment thread web/.env.local
@@ -0,0 +1 @@
NEXT_PUBLIC_API_BASE=http://127.0.0.1:6061
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid committing .env.local with real values; keep only an example file and gitignore .env.local to prevent environment-specific config from leaking into the repo.

Prompt for AI agents
Address the following comment on web/.env.local at line 1:

<comment>Avoid committing .env.local with real values; keep only an example file and gitignore .env.local to prevent environment-specific config from leaking into the repo.</comment>

<file context>
@@ -0,0 +1 @@
+NEXT_PUBLIC_API_BASE=http://127.0.0.1:6061
</file context>
Fix with Cubic

<div className="flex h-full flex-col gap-2">
<button
className="rounded-md bg-accent-500 px-3 py-2 text-sm text-black hover:bg-accent-600"
onClick={async () => {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async click handler lacks error handling; failures from createSession will surface with no user feedback.

Prompt for AI agents
Address the following comment on web/components/chat/SessionList.tsx at line 13:

<comment>Async click handler lacks error handling; failures from createSession will surface with no user feedback.</comment>

<file context>
@@ -0,0 +1,41 @@
+    &lt;div className=&quot;flex h-full flex-col gap-2&quot;&gt;
+      &lt;button
+        className=&quot;rounded-md bg-accent-500 px-3 py-2 text-sm text-black hover:bg-accent-600&quot;
+        onClick={async () =&gt; {
+          const s = await api.createSession();
+          await qc.invalidateQueries({ queryKey: [&#39;sessions&#39;] });
</file context>
Fix with Cubic

onClick={() => onSelect(s.id)}
>
<div className="truncate text-text">{s.title || s.id}</div>
<div className="text-2xs truncate text-text-dim">{new Date(s.updated_at).toLocaleString()}</div>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses undefined Tailwind utility 'text-2xs'; without a fontSize extension this class won’t apply.

Prompt for AI agents
Address the following comment on web/components/chat/SessionList.tsx at line 33:

<comment>Uses undefined Tailwind utility &#39;text-2xs&#39;; without a fontSize extension this class won’t apply.</comment>

<file context>
@@ -0,0 +1,41 @@
+                onClick={() =&gt; onSelect(s.id)}
+              &gt;
+                &lt;div className=&quot;truncate text-text&quot;&gt;{s.title || s.id}&lt;/div&gt;
+                &lt;div className=&quot;text-2xs truncate text-text-dim&quot;&gt;{new Date(s.updated_at).toLocaleString()}&lt;/div&gt;
+              &lt;/button&gt;
+            &lt;/li&gt;
</file context>
Suggested change
<div className="text-2xs truncate text-text-dim">{new Date(s.updated_at).toLocaleString()}</div>
<div className="text-xs truncate text-text-dim">{new Date(s.updated_at).toLocaleString()}</div>
Fix with Cubic

return defaultSettings;
});

useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch {} }, [settings]);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty catch block hides errors during persistence; add a brief comment or handle specific cases to clarify intentional suppression.

Prompt for AI agents
Address the following comment on web/components/app/ThemeProvider.tsx at line 42:

<comment>Empty catch block hides errors during persistence; add a brief comment or handle specific cases to clarify intentional suppression.</comment>

<file context>
@@ -0,0 +1,70 @@
+    return defaultSettings;
+  });
+
+  useEffect(() =&gt; { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch {} }, [settings]);
+  useEffect(() =&gt; { applyTheme(settings); }, [settings]);
+
</file context>
Fix with Cubic

});

useEffect(() => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch {} }, [settings]);
useEffect(() => { applyTheme(settings); }, [settings]);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applying theme changes in useEffect can cause a flash of unstyled/incorrect theme on first paint; consider useLayoutEffect to apply before paint.

Prompt for AI agents
Address the following comment on web/components/app/ThemeProvider.tsx at line 43:

<comment>Applying theme changes in useEffect can cause a flash of unstyled/incorrect theme on first paint; consider useLayoutEffect to apply before paint.</comment>

<file context>
@@ -0,0 +1,70 @@
+  });
+
+  useEffect(() =&gt; { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); } catch {} }, [settings]);
+  useEffect(() =&gt; { applyTheme(settings); }, [settings]);
+
+  const api = useMemo&lt;Ctx&gt;(() =&gt; ({
</file context>
Fix with Cubic

const api = useMemo<Ctx>(() => ({
settings,
set: (k, v) => setSettings((s) => ({ ...s, [k]: v })),
reset: () => setSettings(defaultSettings),
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a shared constant for state reset can result in no re-render if the current state is the same reference; create a fresh object when resetting.

Prompt for AI agents
Address the following comment on web/components/app/ThemeProvider.tsx at line 48:

<comment>Using a shared constant for state reset can result in no re-render if the current state is the same reference; create a fresh object when resetting.</comment>

<file context>
@@ -0,0 +1,70 @@
+  const api = useMemo&lt;Ctx&gt;(() =&gt; ({
+    settings,
+    set: (k, v) =&gt; setSettings((s) =&gt; ({ ...s, [k]: v })),
+    reset: () =&gt; setSettings(defaultSettings),
+  }), [settings]);
+
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant