Skip to content

Add archived channel chat - #7

Merged
ECWireless merged 3 commits into
mainfrom
feat/archived-channel-chat
Aug 2, 2026
Merged

Add archived channel chat#7
ECWireless merged 3 commits into
mainfrom
feat/archived-channel-chat

Conversation

@ECWireless

@ECWireless ECWireless commented Aug 2, 2026

Copy link
Copy Markdown
Member

Summary

  • add private BYOK chat for one archived HTML channel at a time
  • keep membership enforcement and archive access server-side and read-only
  • support short streamed answers with source citations across OpenAI, Anthropic, and Google models
  • add Vercel Web Analytics as a separate commit

Verification

  • pnpm lint
  • pnpm exec tsc --noEmit
  • pnpm build
  • git diff --check
  • manual QA on port 3001

Deployment note

Enable Web Analytics in the Vercel project dashboard before expecting production data.

Summary by CodeRabbit

  • New Features

    • Added private AI chat for archived HTML channels.
    • Supports Anthropic, Google, and OpenAI models with configurable provider, model, and API key settings.
    • Added streaming responses, citations, starter questions, retries, stopping, and session controls.
    • Added “Ask” links for HTML files and responsive archive/chat workspace views.
    • Added analytics tracking.
  • Security & Reliability

    • Added access validation, request limits, concurrency controls, timeouts, and bounded transcript handling.
    • Restricted channel access to valid archived content and private, non-cached responses.

@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
the-valhalla Ready Ready Preview Aug 2, 2026 6:30pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an authenticated channel archive workspace with S3 transcript parsing, provider-backed streaming chat, request validation, rate and concurrency limits, citations, responsive layouts, archive links, and analytics.

Changes

Archived channel chat

Layer / File(s) Summary
Archive validation and transcript pipeline
app/api/shared/channelArchive.ts, app/api/shared/boundedStream.ts, app/api/shared/channelTranscript.ts, app/api/shared/channelTranscriptParser.ts, app/api/channel/route.ts
Validates archived HTML objects, reads bounded S3 content, parses normalized transcripts, and applies private no-store caching to signed archive responses.
Chat contracts and admission controls
app/chatConfig.ts, app/api/chat/provider.ts, app/api/chat/request.ts, app/api/shared/authRateLimit.ts, package.json
Defines supported providers and models, validates normalized chat requests, and adds chat rate and concurrency controls.
Authenticated streaming chat route
app/api/chat/route.ts, app/api/shared/memberAuth.ts
Authenticates member requests, propagates cancellation, bounds request bodies, loads transcripts, creates language models, streams citation-aware responses, and handles cleanup and structured errors.
Channel workspace and chat interface
app/channel/page.tsx, app/channel/ChannelWorkspace.tsx
Adds archive access gates, sandboxed archive viewing, provider setup, streaming chat, citations, retries, session reset, and message composition.
Channel entry points and responsive styling
app/page.tsx, app/globals.css, app/layout.tsx
Adds HTML-only “Ask” links, channel workspace and mobile styling, and analytics URL sanitization.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChannelWorkspace
  participant ChatRoute
  participant AuthRateLimit
  participant ChannelTranscript
  participant LanguageModel
  ChannelWorkspace->>ChatRoute: POST chat request
  ChatRoute->>AuthRateLimit: Check rate and concurrency limits
  ChatRoute->>ChannelTranscript: Retrieve archived transcript
  ChatRoute->>LanguageModel: Generate response with archive evidence
  LanguageModel-->>ChatRoute: Stream citation-aware response
  ChatRoute-->>ChannelWorkspace: Return UI message stream
Loading

Possibly related PRs

Suggested reviewers: jipperism

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding chat functionality for archived channels.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/archived-channel-chat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ECWireless
ECWireless marked this pull request as ready for review August 2, 2026 17:17
Copilot AI review requested due to automatic review settings August 2, 2026 17:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new “archived channel chat” flow that lets authenticated members open a single archived HTML channel, view it read-only, and ask BYOK (bring-your-own-key) AI questions against that channel with streamed responses and message-level citations. It also introduces Vercel Web Analytics.

Changes:

  • Adds /channel UI workspace that embeds the archived HTML in a sandboxed iframe and provides a client-side chat UI that calls a new /api/chat route.
  • Implements server-side transcript extraction from archived HTML exports stored in S3, plus chat request validation, rate limiting, and concurrency limits.
  • Adds AI SDK providers (OpenAI/Anthropic/Google), Cheerio for HTML parsing, and Vercel Analytics; bumps Node engine requirement to >=22.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks new AI SDK / analytics / parsing dependencies and updated transitive graph.
package.json Adds AI/analytics/parser deps and sets engines.node >= 22 to match new packages.
app/page.tsx Adds “Ask” link for HTML channel entries and adjusts card structure/classes.
app/layout.tsx Wires in @vercel/analytics/next <Analytics />.
app/globals.css Styles the new channel workspace + chat UI and updates file-card layout.
app/chatConfig.ts Centralizes provider/model options and provider type guards for chat.
app/channel/page.tsx Adds the /channel route entry point and passes the selected key into the workspace.
app/channel/ChannelWorkspace.tsx Implements the client-side channel viewer + BYOK chat UX and request shaping.
app/api/shared/channelTranscriptParser.ts Parses archived HTML exports into a normalized, bounded transcript with message IDs.
app/api/shared/channelTranscript.ts Reads the channel HTML from S3 with size/time bounds and converts it to a transcript.
app/api/shared/channelArchive.ts Validates a requested key exists in the archived HTML collection in S3.
app/api/shared/boundedStream.ts Adds a reusable bounded stream reader for request/S3 body size + timeout caps.
app/api/shared/authRateLimit.ts Extends existing auth rate limiting with chat budgets and concurrency slots.
app/api/chat/route.ts Adds the server-side streaming chat endpoint with membership enforcement and citations metadata.
app/api/chat/request.ts Validates and bounds chat request payloads (provider/model/key/channelKey/messages).
app/api/chat/provider.ts Creates provider-specific language models from the BYOK API key.
app/api/channel/route.ts Restricts signed URL issuance to archived HTML channels and disables caching via response headers.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/channel/page.tsx
Comment thread app/api/shared/boundedStream.ts
Comment thread app/channel/ChannelWorkspace.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (8)
app/api/shared/channelArchive.ts (1)

10-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider HeadObjectCommand for the existence check.

ListObjectsV2Command with Prefix: key needs s3:ListBucket and returns object metadata for neighbours. A HeadObjectCommand for the exact key answers the same question with one lookup and narrower IAM scope. Also note the !object.Key.endsWith("/") guard is unreachable: object.Key === key already guarantees the .html suffix checked on Line 8.

♻️ Proposed refactor
-import { ListObjectsV2Command } from "`@aws-sdk/client-s3`";
+import { HeadObjectCommand } from "`@aws-sdk/client-s3`";
 
 import { getS3Bucket, s3Client } from "../../config";
 
 export async function isArchivedHtmlChannel(key: string, signal: AbortSignal) {
   if (!key.toLowerCase().endsWith(".html")) return false;
 
-  const result = await s3Client.send(
-    new ListObjectsV2Command({
-      Bucket: getS3Bucket(),
-      MaxKeys: 2,
-      Prefix: key,
-    }),
-    { abortSignal: signal },
-  );
-
-  return Boolean(
-    result.Contents?.some(
-      (object) => object.Key === key && !object.Key.endsWith("/"),
-    ),
-  );
+  try {
+    await s3Client.send(
+      new HeadObjectCommand({ Bucket: getS3Bucket(), Key: key }),
+      { abortSignal: signal },
+    );
+    return true;
+  } catch (error) {
+    if ((error as { name?: string }).name === "NotFound") return false;
+    throw error;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/shared/channelArchive.ts` around lines 10 - 23, Replace the
ListObjectsV2Command-based existence check with a HeadObjectCommand targeting
the exact key, preserving the existing abort signal and bucket configuration.
Update the surrounding logic to return true when the head request succeeds and
false when the object is absent, and remove the unnecessary Contents iteration
and trailing-slash guard.
app/api/chat/request.ts (1)

89-106: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Enforce the per-provider model allowlist declared in app/chatConfig.ts.

MODEL_ID_PATTERN accepts any syntactically valid identifier. CHAT_PROVIDERS[provider].models declares the supported set, but the server never checks it. A client can therefore request any model string for the selected provider, which defeats the allowlist and can produce provider errors or unexpected output sizes that the 400-token cap and 250-word instruction assume. Validate the model against the provider entry.

♻️ Proposed refactor
-import { isInferenceProvider, type InferenceProvider } from "../../chatConfig";
+import {
+  CHAT_PROVIDERS,
+  isInferenceProvider,
+  type InferenceProvider,
+} from "../../chatConfig";
@@
   if (
     !isInferenceProvider(candidate.provider) ||
@@
     !MODEL_ID_PATTERN.test(model) ||
+    !(CHAT_PROVIDERS[candidate.provider].models as readonly string[]).includes(
+      model,
+    ) ||
     !messages
   ) {
     return null;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/chat/request.ts` around lines 89 - 106, Update the validation
condition in the request handler to verify that the trimmed model is included in
the selected provider’s `CHAT_PROVIDERS[provider].models` allowlist, in addition
to passing `MODEL_ID_PATTERN`. Reject the request when the provider entry does
not support the requested model, while preserving the existing validation
behavior.
app/api/chat/route.ts (1)

81-94: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

The full transcript is re-sent on every turn.

buildArchiveEnvelope serializes up to MAX_CHANNEL_TRANSCRIPT_CHARACTERS (320,000) of transcript into the first user message. The route rebuilds it from S3 for each request, so a 12-turn conversation performs 12 object reads, 12 HTML parses, and 12 full-context uploads. Latency and the member's provider cost grow linearly with turns. Consider caching the parsed transcript per channel key with a short TTL, and consider provider prompt caching, which both Anthropic and OpenAI expose for repeated large prefixes.

Also applies to: 194-203

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/chat/route.ts` around lines 81 - 94, Update the transcript handling
around buildArchiveEnvelope and its route call sites to avoid rebuilding and
re-uploading the full transcript on every turn: cache the parsed transcript per
channelKey with a short TTL, reuse valid cached data across requests, and
investigate enabling the provider’s prompt-caching mechanism for the repeated
transcript prefix where supported.
app/api/shared/channelTranscriptParser.ts (1)

83-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Build the transcript label once.

Lines 83-90 and Lines 110-117 construct the same label and line format. The two copies can drift, and the second pass re-walks every message. Collect transcriptLine in the loop and join it at the end.

♻️ Proposed refactor
+function buildTranscriptLine(message: ChannelTranscriptMessage) {
+  const label = [
+    `M:${message.id}`,
+    message.timestamp ? `time:${message.timestamp}` : undefined,
+    `author:${message.author}`,
+  ]
+    .filter(Boolean)
+    .join(" | ");
+  return `[${label}] ${message.content}`;
+}
-    const label = [
-      `M:${id}`,
-      timestamp ? `time:${timestamp}` : undefined,
-      `author:${lastAuthor}`,
-    ]
-      .filter(Boolean)
-      .join(" | ");
-    const transcriptLine = `[${label}] ${content}`;
+    const message: ChannelTranscriptMessage = {
+      author: lastAuthor,
+      content,
+      id,
+      timestamp,
+    };
+    const transcriptLine = buildTranscriptLine(message);
 
     characterCount += transcriptLine.length + 1;
     if (characterCount > MAX_CHANNEL_TRANSCRIPT_CHARACTERS) {
       throw new ChannelTranscriptTooLargeError();
     }
 
-    messages.push({
-      author: lastAuthor,
-      content,
-      id,
-      timestamp,
-    });
+    messages.push(message);
+    lines.push(transcriptLine);
   });
 
   return {
     characterCount,
     messages,
-    text: messages
-      .map((message) => { /* duplicated label logic */ })
-      .join("\n"),
+    text: lines.join("\n"),
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/shared/channelTranscriptParser.ts` around lines 83 - 119, Update the
transcript parsing loop to collect each generated transcriptLine alongside
messages, then build the returned text by joining those collected lines instead
of remapping messages to reconstruct labels. Remove the duplicate
label-formatting logic from the return path while preserving character counting,
message contents, and newline-separated output.
app/api/shared/authRateLimit.ts (2)

117-122: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Name the chat limits and avoid consuming a member token on a global rejection.

Lines 118 and 121 use inline 12 and 240, while the concurrency caps above use named constants. Also, checkAuthRateLimit increments the member counter before the global budget is checked, so a request rejected by the global budget still costs the member one token in the window.

♻️ Proposed refactor
+const CHAT_RATE_LIMIT_PER_MEMBER = 12;
+const CHAT_GLOBAL_BUDGET = 240;
+
 export function checkChatRateLimit(identity: string) {
-  const identityBudget = checkAuthRateLimit("chat", identity, 12);
+  const identityBudget = checkAuthRateLimit(
+    "chat",
+    identity,
+    CHAT_RATE_LIMIT_PER_MEMBER,
+  );
   if (!identityBudget.allowed) return identityBudget;
 
-  return checkAuthGlobalBudget("chat", 240);
+  return checkAuthGlobalBudget("chat", CHAT_GLOBAL_BUDGET);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/shared/authRateLimit.ts` around lines 117 - 122, Update
checkChatRateLimit to use named constants for both the member limit of 12 and
global limit of 240, alongside the existing concurrency-cap constants. Check the
global budget before calling checkAuthRateLimit so globally rejected requests do
not consume a member token, while preserving the existing budget return
behavior.

124-150: 🩺 Stability & Availability | 🔵 Trivial

The concurrency counters are process-local.

activeChatRequests and activeChatRequestsByMember live in module scope. On a serverless or multi-instance deployment each instance keeps its own counters, so the effective global cap is 12 multiplied by the instance count. The same applies to rateLimitEntries and globalBudgets. If the caps must hold across the fleet, back them with shared state, for example Redis or Vercel KV. If the caps are only a per-instance guard, record that intent in a comment.

A second operational note: the counter only decreases when the returned callback runs. If a future code path returns before calling it, the slot leaks for the process lifetime and chat stays unavailable on that instance. A timestamp on each slot plus a sweep of stale slots would make the leak self-healing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/shared/authRateLimit.ts` around lines 124 - 150, Update
acquireChatConcurrencySlot and the related rateLimitEntries/globalBudgets state
so concurrency and rate limits use shared, atomic storage such as Redis or
Vercel KV across instances. Add expiration or stale-slot sweeping so unreleased
slots self-heal; otherwise explicitly document that these limits are
intentionally per-instance guards.
app/globals.css (1)

1222-1241: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Anchor the jump control to the composer.

.chat-jump-latest uses a fixed bottom: 8.25rem that matches the current composer height. The composer textarea sets resize: vertical at Line 1248. When a user enlarges the textarea, the composer grows and the button overlaps the composer or floats inside the message list. Position the button relative to the composer instead of the session container.

💅 Proposed approach
 .chat-composer {
   position: relative;
   margin: 0 1rem;
 }
+
+.chat-composer > .chat-jump-latest {
+  bottom: calc(100% + 0.5rem);
+}

Move the .chat-jump-latest element inside the .chat-composer element in app/channel/ChannelWorkspace.tsx, then drop the fixed bottom: 8.25rem value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/globals.css` around lines 1222 - 1241, Move the `.chat-jump-latest`
element inside the `.chat-composer` element in `ChannelWorkspace.tsx`, then
update `.chat-jump-latest` in `app/globals.css` to remove the fixed `bottom:
8.25rem` positioning and anchor it relative to the composer while preserving the
existing styling.
app/channel/ChannelWorkspace.tsx (1)

103-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Match on stable error codes instead of message text.

The branches match English substrings from server responses. Any wording change on the server silently reroutes an authentication or terminal error into the generic retry branch. The user then sees a retry button for an unrecoverable state. Return a machine-readable code field from /api/chat and branch on that value. Keep the text match as a fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/channel/ChannelWorkspace.tsx` around lines 103 - 136, Update the error
classification logic around the message normalization branches to prefer a
machine-readable code returned by /api/chat, using distinct codes for
authentication, terminal, and retryable errors; retain the existing
serverMessage text matching only as a fallback when no code is available. Ensure
unrecoverable coded errors still return the archive or none actions, while
retryable codes return retry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/chat/route.ts`:
- Line 31: Adjust the timeout budget in the route’s stream configuration and
related timeout handling so the worst-case body read, archive lookup, object
read, and streaming duration remain below maxDuration. Update totalMs and any
corresponding remaining-budget calculation near the stream setup, preserving the
intended timeout error instead of allowing platform termination.

In `@app/api/shared/boundedStream.ts`:
- Around line 24-41: Track whether the read loop completed normally in the
bounded stream logic around reader.read(), setting the completion flag when done
is observed. Before the final options.createTimeoutError() check, require that
the loop did not complete, so a timer firing after the final read cannot turn a
fully consumed body into a timeout.

In `@app/api/shared/channelTranscriptParser.ts`:
- Around line 75-76: Update the fallback assignment in the message ID parsing
flow around rawId and id so invalid or missing data-message-id values use a
distinct prefixed identifier instead of String(index + 1). Preserve valid
numeric IDs unchanged and ensure fallback IDs remain unique per message for
citation resolution.
- Around line 42-49: Update findTimestamp so candidates must match a strict
date-like format before Date.parse is attempted, preventing values such as
avatar, emoji, or reaction-count titles from being selected. Preserve the
existing length limit and finite-date validation, and continue returning the
first valid timestamp candidate.

In `@app/channel/ChannelWorkspace.tsx`:
- Around line 481-487: Update the explanatory copy in ChannelWorkspace to derive
the displayed question-and-answer count from CHAT_HISTORY_MESSAGE_LIMIT instead
of hardcoding 12, accounting for the limit’s message-to-pair relationship so the
text stays accurate when the constant changes.
- Around line 596-611: Remove the nested live-region semantics from the thinking
indicator near `chat-messages`: either move that indicator outside the
`.chat-messages` container or remove its `role="status"` and `aria-live`
attributes. Keep only the outer `role="log"` region, preserving the indicator’s
visual and loading behavior.
- Around line 284-295: Update the access-loss reset effect in ChannelWorkspace
around resetChat to call the useChat stop() function before clearing
activeSettings, the API key, consent, and messages. Ensure any in-flight stream
is aborted before the reset state is applied, while preserving the existing
loading and verified-access guards.

In `@app/chatConfig.ts`:
- Around line 21-25: Update isInferenceProvider to validate that the string is
an own property of CHAT_PROVIDERS rather than using the prototype-chain-aware in
operator, rejecting values such as "constructor" and "toString" while preserving
valid provider checks.
- Around line 1-17: Update the Google entry in CHAT_PROVIDERS so defaultModel
uses a currently supported, non-retiring Gemini model instead of
gemini-2.5-flash; ensure the selected default is included in that provider’s
models list and preserve existing user-selected model compatibility.

In `@app/globals.css`:
- Around line 1157-1185: Update the .chat-thinking-dots > span styles to respect
prefers-reduced-motion by adding a reduced-motion media query that disables the
chat-thinking-dot animation for users who request less motion.

In `@app/layout.tsx`:
- Line 12: Update the Analytics integration in app/layout.tsx to prevent Vercel
Web Analytics from recording the private key query parameter on /channel URLs:
configure its beforeSend hook to remove or redact key while preserving other
analytics data, or exclude Analytics for that route. Ensure page-view URLs never
retain the S3 key.

---

Nitpick comments:
In `@app/api/chat/request.ts`:
- Around line 89-106: Update the validation condition in the request handler to
verify that the trimmed model is included in the selected provider’s
`CHAT_PROVIDERS[provider].models` allowlist, in addition to passing
`MODEL_ID_PATTERN`. Reject the request when the provider entry does not support
the requested model, while preserving the existing validation behavior.

In `@app/api/chat/route.ts`:
- Around line 81-94: Update the transcript handling around buildArchiveEnvelope
and its route call sites to avoid rebuilding and re-uploading the full
transcript on every turn: cache the parsed transcript per channelKey with a
short TTL, reuse valid cached data across requests, and investigate enabling the
provider’s prompt-caching mechanism for the repeated transcript prefix where
supported.

In `@app/api/shared/authRateLimit.ts`:
- Around line 117-122: Update checkChatRateLimit to use named constants for both
the member limit of 12 and global limit of 240, alongside the existing
concurrency-cap constants. Check the global budget before calling
checkAuthRateLimit so globally rejected requests do not consume a member token,
while preserving the existing budget return behavior.
- Around line 124-150: Update acquireChatConcurrencySlot and the related
rateLimitEntries/globalBudgets state so concurrency and rate limits use shared,
atomic storage such as Redis or Vercel KV across instances. Add expiration or
stale-slot sweeping so unreleased slots self-heal; otherwise explicitly document
that these limits are intentionally per-instance guards.

In `@app/api/shared/channelArchive.ts`:
- Around line 10-23: Replace the ListObjectsV2Command-based existence check with
a HeadObjectCommand targeting the exact key, preserving the existing abort
signal and bucket configuration. Update the surrounding logic to return true
when the head request succeeds and false when the object is absent, and remove
the unnecessary Contents iteration and trailing-slash guard.

In `@app/api/shared/channelTranscriptParser.ts`:
- Around line 83-119: Update the transcript parsing loop to collect each
generated transcriptLine alongside messages, then build the returned text by
joining those collected lines instead of remapping messages to reconstruct
labels. Remove the duplicate label-formatting logic from the return path while
preserving character counting, message contents, and newline-separated output.

In `@app/channel/ChannelWorkspace.tsx`:
- Around line 103-136: Update the error classification logic around the message
normalization branches to prefer a machine-readable code returned by /api/chat,
using distinct codes for authentication, terminal, and retryable errors; retain
the existing serverMessage text matching only as a fallback when no code is
available. Ensure unrecoverable coded errors still return the archive or none
actions, while retryable codes return retry.

In `@app/globals.css`:
- Around line 1222-1241: Move the `.chat-jump-latest` element inside the
`.chat-composer` element in `ChannelWorkspace.tsx`, then update
`.chat-jump-latest` in `app/globals.css` to remove the fixed `bottom: 8.25rem`
positioning and anchor it relative to the composer while preserving the existing
styling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5006d636-27c9-4337-86bb-37317b75bc12

📥 Commits

Reviewing files that changed from the base of the PR and between 35c38ee and 7b45c0d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (16)
  • app/api/channel/route.ts
  • app/api/chat/provider.ts
  • app/api/chat/request.ts
  • app/api/chat/route.ts
  • app/api/shared/authRateLimit.ts
  • app/api/shared/boundedStream.ts
  • app/api/shared/channelArchive.ts
  • app/api/shared/channelTranscript.ts
  • app/api/shared/channelTranscriptParser.ts
  • app/channel/ChannelWorkspace.tsx
  • app/channel/page.tsx
  • app/chatConfig.ts
  • app/globals.css
  • app/layout.tsx
  • app/page.tsx
  • package.json

Comment thread app/api/chat/route.ts
Comment thread app/api/shared/boundedStream.ts
Comment thread app/api/shared/channelTranscriptParser.ts
Comment thread app/api/shared/channelTranscriptParser.ts Outdated
Comment thread app/channel/ChannelWorkspace.tsx Outdated
Comment thread app/channel/ChannelWorkspace.tsx
Comment thread app/chatConfig.ts
Comment thread app/chatConfig.ts
Comment thread app/globals.css
Comment thread app/layout.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/channel/ChannelWorkspace.tsx (1)

287-296: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Clear the chat error during an access reset.

The reset clears messages and settings, but it does not clear error. changeSettings calls clearError() separately, so the old error can survive a wallet disconnect. After reconnecting, a terminal error can remain visible and keep the new composer disabled. Call clearError() in the reset callback and add it to the effect dependencies.

Proposed fix
     const resetChat = window.setTimeout(() => {
+      clearError();
       setActiveSettings(null);
       setApiKey("");
       setConsentGiven(false);
       setMessages([]);
     }, 0);
 
     return () => window.clearTimeout(resetChat);
-  }, [hasVerifiedAccess, isSessionLoading, setMessages, stop]);
+  }, [clearError, hasVerifiedAccess, isSessionLoading, setMessages, stop]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/channel/ChannelWorkspace.tsx` around lines 287 - 296, Update the
access-reset callback in ChannelWorkspace’s effect to call clearError()
alongside resetting messages, settings, API key, and consent state, then add
clearError to the effect dependency array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/channel/ChannelWorkspace.tsx`:
- Around line 287-296: Update the access-reset callback in ChannelWorkspace’s
effect to call clearError() alongside resetting messages, settings, API key, and
consent state, then add clearError to the effect dependency array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d4cc6f5-e057-446a-ac25-805f307eb01b

📥 Commits

Reviewing files that changed from the base of the PR and between 7b45c0d and d1aec07.

📒 Files selected for processing (7)
  • app/api/chat/route.ts
  • app/api/shared/boundedStream.ts
  • app/api/shared/channelTranscriptParser.ts
  • app/api/shared/memberAuth.ts
  • app/channel/ChannelWorkspace.tsx
  • app/chatConfig.ts
  • app/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/chatConfig.ts
  • app/api/shared/channelTranscriptParser.ts
  • app/api/shared/boundedStream.ts
  • app/api/chat/route.ts

@ECWireless
ECWireless merged commit ac36a72 into main Aug 2, 2026
3 checks passed
@ECWireless
ECWireless deleted the feat/archived-channel-chat branch August 2, 2026 18:37
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.

2 participants