Sign in
Join now
scn-chat
sharedcomputer.network
/
Star
Starred
Star
Something went wrong. Try again.
Fork
0
Eye filled
Watch
Watching
Watch
ATProto-based AI Assistant Chat interface.
Star
Starred
Star
Something went wrong. Try again.
Fork
Eye filled
Watch
Watching
Watch
Overview
Overview
Issues
Issues
1
1
Pulls
Pulls
Pipelines
Pipelines
scn-chat
/
apps
/
web
/
src
/
parts
1 folder
·
21 files
at
admin-config-export
pouriade.com
·
A reply's step dots stay under the sticky header and composer as the chat scrolls beneath them. The rail is now its own stacking context, so the z-index that lifts each dot over its segment no longer ranks it against the whole page.
·
2w ago
27f7d172
tools
The chat list and its rows do less work, from an audit against React performance practice. Opening a search hides the chat list inside React's Activity rather than unmounting it, so closing the search brings the chats back as they were rather than mounting every row again. A chat row no longer holds three mutations: renaming and tagging are an EditChat card and deleting a DeleteChat card, each holding its own, so an idle row holds none, and cancelling a failed change clears its error with the card. Picking a tag keeps the rows shown until its chats arrive, rather than a skeleton between. The month headings of older chats come from two formatters made once, rather than one made for each chat on every change to the list. A reply's sources and a search's results take the fold's ref apart, so React Compiler compiles them rather than silently leaving them out. The conversation's rename, instructions, and sync call their mutations' own functions, so the header no longer renders with every state either passes through, and its jump outline is built only while the jump stack is open. The search spec says the list stays mounted, and the list test checks it is hidden.
2w ago
BlockActions.tsx
Give a reply's code blocks and tables icon actions in their corner, on a card of the block's own color, shown on hover and always on a touch screen. A finished code block copies its code or downloads it as a file named for its language, as code.py. A table's Copy opens a menu of Markdown, CSV, or TSV, showing a check once copied, and its Download one of CSV or Markdown, read from the table as it shows through Streamdown's table helpers behind our own buttons and menu. A table now fills the column inside a frame that scrolls when it is wider, so its rows run to its edge, its headings stay on one line, and the last leaves room for the actions. Copy's icon swap moves into CopyIconSwap so the table's menu shares it.
2w ago
download.ts
Give a reply's code blocks and tables icon actions in their corner, on a card of the block's own color, shown on hover and always on a touch screen. A finished code block copies its code or downloads it as a file named for its language, as code.py. A table's Copy opens a menu of Markdown, CSV, or TSV, showing a check once copied, and its Download one of CSV or Markdown, read from the table as it shows through Streamdown's table helpers behind our own buttons and menu. A table now fills the column inside a frame that scrolls when it is wider, so its rows run to its edge, its headings stay on one line, and the last leaves room for the actions. Copy's icon swap moves into CopyIconSwap so the table's menu shares it.
2w ago
FileView.tsx
A file in a sent message shows as the chip it was in the composer, with its icon, name, and kind and size, as "PDF · 1.2 MB", rather than a bare link holding its name. The chip moves to src/shared/ui as FileChip, which the composer and the message share. The whole chip is a link: a PDF opens in a new tab in the browser's own viewer, ending in an arrow out, and any other file downloads under its name, ending in a download arrow. Files side by side share a row that wraps toward the message's edge. The blob routes, the owner's and the shared one, serve a PDF inline and take application/pdf as a type, so a spaces user's PDF reaches the browser as one. The attachments, web UI, and styling specs say so.
2w ago
fold.ts
A reply's sources sit behind their count, which opens like a dropdown onto a numbered list with one source to a line, rather than badges wrapped side by side under a count. The count is a details summary with a chevron, and it starts closed, so the sources stay out of the reply's way until asked for. Each row is as wide as its link, in the well's grey, so its hover tint covers the link rather than the line. The list no longer folds past seven, since opening the count already asks for every source; a web search's results still fold. The web UI spec says so, and the message test checks the sources start closed and open to show all ten.
2w ago
highlight.ts
Fixes from the review of the performance work. A reply's stream batch is sent on the next frame or after 100ms, whichever comes first, so a reply streamed in a hidden tab, where frames don't run, still reaches its views and their step clocks as it goes, and a cancelled stream closes its connection and sends nothing more, where a late event could write to the cancelled stream and throw. A code block is colored from its first frame only when its finished code was highlighted before, as when a reply's record replaces its stream, since Shiki answers through a promise even once its grammar has loaded: highlight.ts keeps the tokens of the last 200 finished blocks for that, and the first render reads them without starting a second highlight, which it did for every new block, and the web UI spec now says so rather than claiming every block of a loaded language. The step clock keeps the times of the latest 500 steps, forgetting the oldest first. Starting or ending a regeneration rerenders only the Try again buttons, which read it from a Regenerating context, rather than every message. Both blob routes set their headers through one setBlobHeaders, which adds Vary: Cookie, so another account signed in on the same browser asks the server again rather than reading a blob from the cache. A row's sibling fields come from Siblings, the hook behind a step's body is named body, the chat list asks router.matchRoute whether a deleted chat is open rather than casting the router's params, sign-out passes the mutation's own function, and ConversationView takes pick and send out of chat with the others. The web UI spec says images load as they near the screen, and the attachments spec names Vary: Cookie. Tests cover the hidden tab, the cancelled stream, and the step clock's limit.
2w ago
ImageView.tsx
Pictures in a chat load once they near the screen and decode off the main thread, so opening a long chat no longer fetches and decodes every picture in it. Blobs are served with Cache-Control: private, max-age=31536000, immutable, on the owner's route and the shared one, since their URL names the CID and their bytes never change, so the browser keeps them rather than downloading each picture again on every visit. The attachments and sharing specs say so, and the route tests check the header.
2w ago
index.ts
Port how messages render from design-reference. Text, sources, images, and files show through partViews, and each run of reasoning and tool calls through StepsView, design-reference's WorkSteps in src/parts: a live numbered timeline with timers while the reply streams, which gives way to its text, and a finished reply's steps behind a footer button that names and times them. Your messages are bubbles with quotes in a well, a reply names its model after a dot that breathes while it is pending, sources are numbered badges, a failed reply offers Try again, and messages that arrive fade in. Markdown gets its own sheet, per-block direction, and a Copy on code blocks. The stream builds parts in the record's shape, now following part and queued events, and says when it lost the live stream. Reasoning and tool parts have no part view of their own.
2w ago
links.ts
A reply's tool calls show as what they did. A web search reads "Searched the web" with its query and opens onto its results, each its title linking to the page in a new tab, its site, and its snippet as plain text, or "No results." A page fetch reads "Read a page" with its address and opens onto the page's title and site, which of its characters the call read when there were more, and the text it read as plain text. The live line says "Searching the web…" and "Reading a page…", and a finished reply's steps are summed up as "Thought for 12s · Searched the web ×2 · Read a page". Each tool's entry in toolViews, in parts/tools/, gives its label, its activity, the glimpse of its input on its row, and its view, and a tool without one reads by its name with its input and output as before, which a view also falls back to for an error, output kept in a blob, or output it can't read. Each call has one status from toolStatus, running, done, error, or incomplete, which its badge, number, timer, and the live line all read, so calls made at once all show as running until their results arrive, where before only the last did, and a call cut off without its result says incomplete. Tool output stays text: no HTML, Markdown, images, or favicons, and links only to web pages. The tool views spec, split out of the extending spec, describes it, the plugin guide tells tool authors where a view goes, and the tests render replies through MessageView.
2w ago
markdown.css
Revert "A streaming reply's words come into focus as they arrive, fading in from a 4px blur as Streamdown's blurIn does, rather than only fading. The theme gains --animate-blur-in, its blur-in keyframes, and a --blur-in token for how blurred a word starts, since the app doesn't load Streamdown's stylesheet. Timing, the cascade, list markers, and reduced motion are as before."
2w ago
markdown.tsx
Revert "A streaming reply's words come into focus as they arrive, fading in from a 4px blur as Streamdown's blurIn does, rather than only fading. The theme gains --animate-blur-in, its blur-in keyframes, and a --blur-in token for how blurred a word starts, since the app doesn't load Streamdown's stylesheet. Timing, the cascade, list markers, and reduced motion are as before."
2w ago
math.ts
Render replies and reasoning with Streamdown, which completes the Markdown a stream leaves half written, so an open ** already shows bold and a link still being typed reads as its text, and renders a reply block by block, so the blocks already written don't render again. Every element stays ours, styled by markdown.css, with Streamdown's controls and link confirmation off and rehype-raw dropped, so HTML in a reply shows as the text it is. Fenced code is highlighted by Shiki through @streamdown/code, loaded the first time a code block renders, with a CSS variables theme whose colors are new --color-code-* tokens in the theme, each clearing 4.5:1 in both modes, and a block still streaming highlights as it grows and offers Copy once its fence closes. Math between $$ renders with KaTeX, loaded the first time a text holds some, and @streamdown/cjk fixes emphasis beside CJK punctuation. A formula holds its hidden MathML, and both scroll panels are containing blocks, so nothing placed absolutely can stretch the page past the panel's scroll. ADR 0005 records the choice.
2w ago
no-raw.ts
Load less and rerender less. The new-chat page no longer downloads the Markdown renderer: the conversation and the search results load with React.lazy, the chat route's loader starts the conversation's download, the new-chat page starts it once it shows, and opening the search starts the results', so what / downloads drops from 335 KB to 184 KB with brotli. Streamdown imports rehype-raw for its defaults, which markdown.tsx leaves out, so vite.config.ts aliases it to an empty stand-in in parts/no-raw.ts, keeping parse5 out of the build and the Markdown chunk from 163 KB to 71 KB gzipped. The logo's paths are rounded to two decimals, from 10.0 KB to 6.6 KB gzipped. The chat routes' loader starts the models, attachment types, and profile queries without waiting for them, and their head preconnects to the avatar's CDN. attachmentTypesQuery moves to its own module, so the loader doesn't bring the store's hooks into the entry. Pointing at or focusing a link preloads its route, the query cache deciding what is stale.
2w ago
no-wasm.ts
Shiki's Oniguruma engine, a 600 KB wasm, is no longer built. Shiki's full bundle names it for its shorthand highlighter, which the app never uses, since @streamdown/code highlights with Shiki's JavaScript engine, so the wasm was built and precompressed on every build but never loaded. vite.config.ts aliases shiki/wasm to a stand-in, parts/no-wasm.ts, that fails to load with a message saying why, so anything that did reach for the engine would say so rather than highlight nothing. Code built through the alias still highlights in Chrome. The web UI spec says so beside the rehype-raw stand-in.
2w ago
quotes.ts
Port how messages render from design-reference. Text, sources, images, and files show through partViews, and each run of reasoning and tool calls through StepsView, design-reference's WorkSteps in src/parts: a live numbered timeline with timers while the reply streams, which gives way to its text, and a finished reply's steps behind a footer button that names and times them. Your messages are bubbles with quotes in a well, a reply names its model after a dot that breathes while it is pending, sources are numbered badges, a failed reply offers Try again, and messages that arrive fade in. Markdown gets its own sheet, per-block direction, and a Copy on code blocks. The stream builds parts in the record's shape, now following part and queued events, and says when it lost the live stream. Reasoning and tool parts have no part view of their own.
2w ago
SourceView.tsx
A reply's tool calls show as what they did. A web search reads "Searched the web" with its query and opens onto its results, each its title linking to the page in a new tab, its site, and its snippet as plain text, or "No results." A page fetch reads "Read a page" with its address and opens onto the page's title and site, which of its characters the call read when there were more, and the text it read as plain text. The live line says "Searching the web…" and "Reading a page…", and a finished reply's steps are summed up as "Thought for 12s · Searched the web ×2 · Read a page". Each tool's entry in toolViews, in parts/tools/, gives its label, its activity, the glimpse of its input on its row, and its view, and a tool without one reads by its name with its input and output as before, which a view also falls back to for an error, output kept in a blob, or output it can't read. Each call has one status from toolStatus, running, done, error, or incomplete, which its badge, number, timer, and the live line all read, so calls made at once all show as running until their results arrive, where before only the last did, and a call cut off without its result says incomplete. Tool output stays text: no HTML, Markdown, images, or favicons, and links only to web pages. The tool views spec, split out of the extending spec, describes it, the plugin guide tells tool authors where a view goes, and the tests render replies through MessageView.
2w ago
step-clock.ts
Fixes from the review of the performance work. A reply's stream batch is sent on the next frame or after 100ms, whichever comes first, so a reply streamed in a hidden tab, where frames don't run, still reaches its views and their step clocks as it goes, and a cancelled stream closes its connection and sends nothing more, where a late event could write to the cancelled stream and throw. A code block is colored from its first frame only when its finished code was highlighted before, as when a reply's record replaces its stream, since Shiki answers through a promise even once its grammar has loaded: highlight.ts keeps the tokens of the last 200 finished blocks for that, and the first render reads them without starting a second highlight, which it did for every new block, and the web UI spec now says so rather than claiming every block of a loaded language. The step clock keeps the times of the latest 500 steps, forgetting the oldest first. Starting or ending a regeneration rerenders only the Try again buttons, which read it from a Regenerating context, rather than every message. Both blob routes set their headers through one setBlobHeaders, which adds Vary: Cookie, so another account signed in on the same browser asks the server again rather than reading a blob from the cache. A row's sibling fields come from Siblings, the hook behind a step's body is named body, the chat list asks router.matchRoute whether a deleted chat is open rather than casting the router's params, sign-out passes the mutation's own function, and ConversationView takes pick and send out of chat with the others. The web UI spec says images load as they near the screen, and the attachments spec names Vary: Cookie. Tests cover the hidden tab, the cancelled stream, and the step clock's limit.
2w ago
steps.ts
A web search's results and a reply's sources show their first seven, and the rest fold behind a button that shows them in place and becomes "Show less": "Show 3 more" under the results, and a "+3 more" badge at the end of the sources, whose count still says how many there are in all. A list that would hide only one shows it instead, opening one moves focus to the first link it showed, and both fold through useFold in parts/fold.ts. A thought now reads as a tool step does: a row with its chevron, "Thought for 12s" or "Thinking", and a muted glimpse of its first line with its Markdown left out, which opens onto the whole thought, in place of three lines fading out under a Show more. The tool views and web UI specs describe both, and the tests cover the folds and the thought's row.
2w ago
TextView.tsx
Render replies and reasoning with Streamdown, which completes the Markdown a stream leaves half written, so an open ** already shows bold and a link still being typed reads as its text, and renders a reply block by block, so the blocks already written don't render again. Every element stays ours, styled by markdown.css, with Streamdown's controls and link confirmation off and rehype-raw dropped, so HTML in a reply shows as the text it is. Fenced code is highlighted by Shiki through @streamdown/code, loaded the first time a code block renders, with a CSS variables theme whose colors are new --color-code-* tokens in the theme, each clearing 4.5:1 in both modes, and a block still streaming highlights as it grows and offers Copy once its fence closes. Math between $$ renders with KaTeX, loaded the first time a text holds some, and @streamdown/cjk fixes emphasis beside CJK punctuation. A formula holds its hidden MathML, and both scroll panels are containing blocks, so nothing placed absolutely can stretch the page past the panel's scroll. ADR 0005 records the choice.
2w ago
tool-status.ts
The web app's message records are typed as the lexicon's MessageRecord, and a conversation's info as InfoRecord, from the local copy, which holds only records the server validated, through BranchMessage to the message, status, details, part, and tool views, so their reads need no casts. src/shared/message.ts holds what a status means, isPending and isFinished, which counts a status another client wrote that this app doesn't know as neither, along with a message's parts, whether it is encrypted, and who wrote it, in place of the checks written out in the message view, the message actions, the chat, the reply follower, and tool status. Tests build records with messageRecord in tests/helpers/records.ts, and the status rules are tested once.
2w ago
types.ts
The web app's message records are typed as the lexicon's MessageRecord, and a conversation's info as InfoRecord, from the local copy, which holds only records the server validated, through BranchMessage to the message, status, details, part, and tool views, so their reads need no casts. src/shared/message.ts holds what a status means, isPending and isFinished, which counts a status another client wrote that this app doesn't know as neither, along with a message's parts, whether it is encrypted, and who wrote it, in place of the checks written out in the message view, the message actions, the chat, the reply follower, and tool status. Tests build records with messageRecord in tests/helpers/records.ts, and the status rules are tested once.
2w ago
WorkSteps.tsx
A reply's step dots stay under the sticky header and composer as the chat scrolls beneath them. The rail is now its own stacking context, so the z-index that lifts each dot over its segment no longer ranks it against the whole page.
2w ago