Chat Components

Purpose and Scope

OpenWiki exposes repository chat in two user-facing shapes: a small launcher that appears over wiki pages, and a dedicated full-page chat route for longer conversations. The launcher solves the discoverability problem by keeping an “Ask a question...” affordance available near the bottom of the repository wiki. The full-page view solves the interaction problem by giving the conversation enough room for message history, streamed assistant output, citations, tool-call metadata, retry/resume state, and prompt suggestions. Together they make repository chat feel like part of the wiki rather than a separate support surface.

Sources: app/components/repo-chat.tsx, app/components/repo-chat-full-page.tsx

The component split is intentionally narrow. RepoChat is a client component, but it does not own chat state or networking; it renders a next/link to the route that hosts the complete experience. RepoChatFullPage is also a client component and owns the conversational state needed after navigation. It defines local message types, stream-event types, active-run persistence, character limits, reconnect constants, storage prefixes, and the default suggested questions shown to users. That means wiki pages can stay lightweight while the expensive, stateful behavior is loaded only when the reader opens chat.

Sources: app/components/repo-chat.tsx, app/components/repo-chat-full-page.tsx

Relevant Source Files

  • app/components/repo-chat.tsx — Defines the floating repository chat launcher. It accepts chatHref and repoLabel, renders a prefetched Link, and provides an accessible label for the target repository.
  • app/components/repo-chat-full-page.tsx — Defines the full-page chat experience, including message and stream types, React state, persistence references, input limits, reveal timing, stream retry constants, and prompt suggestions.
  • app/(wiki)/[owner]/[repo]/chat/page.tsx — Defines the dynamic chat route for a repository, derives the GitHub URL and navigation links from route params, reads an optional q query parameter, and mounts RepoChatFullPage.
  • lib/chat-rate-limit.ts — Defines the server-side rate-limit policy that chat requests must satisfy, including default hourly and daily limits, environment-variable overrides, and user-facing limit messages.

Component Roles

The launcher component is optimized for being present without interrupting reading. It uses fixed positioning, a maximum responsive width, border and backdrop styling, and a single truncated text span. The aria-label includes the repository label, so assistive technology announces the action in context rather than as a generic link. Because it uses prefetch, opening chat from a repository page can be made faster by letting Next.js prepare the target route ahead of time. The launcher therefore acts as a bridge from passive documentation consumption to interactive exploration.

Sources: app/components/repo-chat.tsx

The full-page component is the state owner for active conversations. Its local ChatMessage model supports user and assistant roles, optional citations by source path, optional tool calls, and assistant states such as thinking, complete, error, and stopped. This shape is important because repository chat is not just a plain text transcript. OpenWiki can show progressive assistant output, communicate incomplete or stopped runs, and attach evidence metadata that points back to files used in the answer. The type definitions make those UI states explicit before the rendering code consumes them.

Sources: app/components/repo-chat-full-page.tsx

The same file also declares a RepoMessageStreamEvent type, which reflects the streaming protocol consumed by the client. Events can carry a current message, accumulated messageSoFar, a result, a status, or actions. A separate ChatSession tracks sessionId, continuationToken, and streamIndex, while PersistedActiveRun records the assistant message being updated and a timestamp. These structures explain why the component has refs for aborting, resuming, and tracking active runs: streamed chat can disconnect, and the UI needs a stable way to recover without losing the in-progress assistant response.

Sources: app/components/repo-chat-full-page.tsx

Full-Page Route Flow

The repository chat page lives under the wiki route tree at app/(wiki)/[owner]/[repo]/chat/page.tsx. It is declared force-dynamic, which is appropriate for a route that depends on live query parameters and interactive chat state rather than purely static wiki content. The page awaits params and searchParams, constructs repoLabel as owner/repo, constructs the canonical GitHub repository URL, derives the repository wiki href with getRepoHref, and appends /chat for the active chat href. This makes the route deterministic from the URL while still allowing dynamic rendering.

Sources: app/(wiki)/[owner]/[repo]/chat/page.tsx

The route also connects chat to the shared OpenWiki navigation. It renders OpenWikiNavbar with activeMode: "chat", the wiki href, chat href, repository label, and an owner avatar fallback URL. After the navbar, it mounts RepoChatFullPage with three values: initialQuestion, repoLabel, and repoUrl. The optional q query parameter becomes initialQuestion, so other pages or links can deep-link directly into a prefilled or auto-sent repository question flow without requiring the user to retype the prompt.

Sources: app/(wiki)/[owner]/[repo]/chat/page.tsx, app/components/repo-chat-full-page.tsx

State, Streaming, and Persistence Details

RepoChatFullPage starts with several pieces of React state: messages, input, isSending, isHydrated, and revealingMessageId. It also keeps refs for the current AbortController, the persisted active run, the initial-question send guard, the current message array, and whether resume has started. The distinction between state and refs matters. State drives rendering, while refs preserve mutable control data across renders without causing additional UI updates. This is especially useful for streaming, where message chunks, abort signals, and reconnect metadata can change frequently.

Sources: app/components/repo-chat-full-page.tsx

The persistence path is visible in the wrapper around setMessages. Every message update is mirrored into messagesRef.current and then passed to persistSnapshot, which writes the repository-specific snapshot through writePersistedRepoChat. Active-run updates follow the same pattern through setActiveRun and updateActiveRunStreamIndex. On hydration, the component resets resume state, reads persisted chat data for the current repoUrl, restores messages when available, and restores an active run if one was saved. The storage key is namespaced by CHAT_STORAGE_PREFIX and versioned with CHAT_STORAGE_VERSION, which allows future migrations without colliding with unrelated browser data.

Sources: app/components/repo-chat-full-page.tsx

The streaming constants describe the user experience OpenWiki is trying to protect. MAX_CHAT_MESSAGE_CHARS caps the input at 8,000 characters. REVEAL_TICK_MS controls how quickly streamed text is revealed. STREAM_OPEN_RETRYABLE_STATUS lists HTTP statuses that can be retried when opening a stream, including temporary or conflict-style responses such as 409, 425, and 503. Disconnect handling is bounded by STREAM_DISCONNECT_RECONNECT_ATTEMPTS, STREAM_IDLE_TIMEOUT_MS, and STREAM_RECONNECT_DELAY_MS. These values keep the UI resilient to transient agent or network behavior while avoiding infinite reconnect loops.

Sources: app/components/repo-chat-full-page.tsx

Rate Limit Signals for the UI

The visual components do not implement rate limiting themselves, but they sit in front of a server-side chat policy defined in lib/chat-rate-limit.ts. The policy is enabled by default unless OPENWIKI_CHAT_RATE_LIMIT_ENABLED is set to a falsey non-enabled value. Defaults allow 40 messages per client per hour, 200 per client per day, and 600 globally per hour. Each request is associated with a hashed client key and repository full name before reserving an attempt in storage. If storage returns a denied reservation, ChatRateLimitError carries the limit, reset time, retry-after seconds, and limit scope.

Sources: lib/chat-rate-limit.ts

This matters for component behavior because rate-limit failures are user-facing chat errors, not generic crashes. The rate-limit module formats different messages depending on whether the OpenWiki instance is globally busy, the client reached a daily cap, or the client sent too many messages recently. It also exports chatRateLimitedCode, which gives the API layer a stable error code to return. When developing chat UI changes, treat rate-limit responses as a normal terminal state for a message attempt: the input should not appear to stream forever, and the displayed error should preserve the wait time supplied by the server.

Sources: lib/chat-rate-limit.ts

Compact Reference

SurfaceContractNotes
RepoChatProps: chatHref: string, repoLabel: stringFloating launcher; renders a prefetched Link with repository-specific aria-label.
RepoChatFullPageProps: initialQuestion?: string, repoLabel: string, repoUrl: stringFull conversation UI and state owner for a repository.
ChatMessageid, role, content, optional state, citations, toolCallsSupports progressive assistant output and source-aware answers.
ChatSessionOptional sessionId, optional continuationToken, streamIndexTracks resumable streaming progress.
Chat route pageParams: owner, repo; query: q?Builds https://github.com/{owner}/{repo} and passes q as initialQuestion.
Rate-limit configOPENWIKI_CHAT_RATE_LIMIT_ENABLED, OPENWIKI_CHAT_RATE_LIMIT_CLIENT_DAILY, OPENWIKI_CHAT_RATE_LIMIT_CLIENT_HOURLY, OPENWIKI_CHAT_RATE_LIMIT_GLOBAL_HOURLYServer-side limits that shape chat error states.

Sources: app/components/repo-chat.tsx, app/components/repo-chat-full-page.tsx, app/(wiki)/[owner]/[repo]/chat/page.tsx, lib/chat-rate-limit.ts

Implementation Guidance and Next Steps

When adding or changing chat UI behavior, keep the launcher and full-page responsibilities separate. The launcher should remain a small navigation affordance that can be embedded on wiki pages without carrying chat session complexity. Conversation features such as suggestions, streaming, persistence, cancellation, and reconnect handling belong in RepoChatFullPage, where the repository URL and message history are already available. If a feature requires server policy, such as quotas or authentication, wire it through the API and rate-limit modules rather than duplicating policy in the browser.

Sources: app/components/repo-chat.tsx, app/components/repo-chat-full-page.tsx, lib/chat-rate-limit.ts

For route-level changes, preserve the URL-derived repository identity. The chat page currently computes all repository labels, links, and the GitHub URL from owner and repo, then passes those derived values down to the component. That keeps deep links, navbar state, and chat context aligned. Useful follow-up pages are repository-chat for the end-to-end product behavior, chat-api for request and streaming server details, rate-limits-and-auth for policy tuning, and wiki-routing-and-pages for how this route fits into the public wiki tree.

Sources: app/(wiki)/[owner]/[repo]/chat/page.tsx