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
Commits on
admin-config-export
hadsie.com
e022407c
Copied
Failed
Copy SHA
Add site-config export / import via CLI.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
27f7d172
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
3c7e3ad4
Copied
Failed
Copy SHA
Settings and admin forms keep Save disabled until the form differs from what was saved, and say "Unsaved changes" beside it until then. SaveBar reads this from its form's context, and a form that adds, such as Issue key or Add plugin, passes adds so it never says so. Leaving a page doesn't ask. Preferences now starts over from the stored record after a save, as the other forms
do.
do.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
e3dfc6e7
Copied
Failed
Copy SHA
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."
before."
Copy SHA
Browse repository
committed
1w ago
pouriade.com
185b9492
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
59e19733
Copied
Failed
Copy SHA
A streaming reply's words fade in as they arrive, over the theme's enter duration, with Streamdown's 40ms between words, so a burst of them lands as a short cascade. markdown.css animates Streamdown's word spans with --animate-fade-in, since the app doesn't load Streamdown's stylesheet, and fades a list item's marker in by its color, which is all ::marker takes. Nothing animates under reduced motion. Code blocks and math appear as before, and a finished part renders without the spans. Two tests that found streamed text by its exact node now look past the word spans.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
bfdb9889
Copied
Failed
Copy SHA
The steps rail takes its lengths from the spacing scale, so the theme check passes again. The dot's box is 1.25rem wide rather than 1.2rem, which moves the dot 0.4px, and the segment between dots sits at -start-4.25 and top-5.5, so it stays centered under the dot.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
21bb7b6d
Copied
Failed
Copy SHA
Release 0.2.2
v0.2.2
Copy SHA
Browse repository
committed
1w ago
pouriade.com
6b3f3148
Copied
Failed
Copy SHA
A long code block streams with about half the CPU it took. Each of its lines is a block of its own, so a character arriving lays out and paints its line alone, rather than the browser laying out and repainting the whole block, and its lines render in memoized groups of 32, so React rerenders only the last group rather than visiting every line. Each line still ends in its newline, so text selected and copied from a block keeps its blank lines. Streaming a 2,000-line block in a production build, the main thread went from 67% to 26% busy, with layout from 10 s to 1 s and paint from 15.6 s to 3.7 s.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
ceed41b6
Copied
Failed
Copy SHA
The composer decides whether a message can be sent once, as canSend, which both disables Send and stops a submit, so a new reason not to send can't reach one and miss the other. Submitting still checks on its own for a send already on its way and for a running reply, whose Stop takes Send's place.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
fceb397e
Copied
Failed
Copy SHA
A reply the follower followed since it was sent stops being followed once the local copy holds its final record, so what streamed of it, which can include whole fetched pages, isn't kept in memory beside the record until the chat closes. The follower's tests run through useChat over the test store, rather than mounting useConversation by hand and holding the messages in state, and a test checks a finished reply's stream goes. specs/web-ui.md says the follower refreshes through the store's refresh intent so the store's hooks know nothing of streams, rather than merging the two.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
46d94593
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
94ef66a2
Copied
Failed
Copy SHA
A pending reply is drawn by one module, PendingReply.tsx: its waiting or live header, its stream, and the lines about a wait, a busy provider, or a lost stream, until its finished record replaces it. MessageView uses it while a record is pending, and it follows the conversation the FollowedConversation context names, which ConversationView provides, so MessageView and BranchView no longer take pending and live slots and BranchRow no longer checks the status. The stream's part views get the pending record rather than a made-up one, and the model's name comes from MessageView alone. LiveAuthor.tsx, ReplyStream.tsx, and StreamingReply.tsx are gone. A conversation test streams a reply, then has its record arrive, and checks the time it thought carries over and its text shows once.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
c3d657e6
Copied
Failed
Copy SHA
The browser store's seam has a second adapter. The worker exposes every StoreCore method through one guard in store/calls.ts, which fails while another tab holds the store and reports an ended session, rather than restating each method, and StoreClient holds them as calls rather than worker. testStore in tests/helpers/store.ts runs the same logic and guard over better-sqlite3 with a fake server API, and the component tests that built their own store, for the conversation, chat list, app, chat, reply follower, settings, and the store's hooks, use it, so they go through the real reads, syncs, and change events. The chat list's search tests check the list is visible again rather than only present, since it stays mounted while hidden.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
ba9ccca3
Copied
Failed
Copy SHA
Release 0.2.1
v0.2.1
Copy SHA
Browse repository
committed
1w ago
pouriade.com
3c282993
Copied
Failed
Copy SHA
Small settings, admin, and sidebar fixes. Renaming a chat from the sidebar won't save an empty title, which marked the chat user-titled and kept it from ever being titled automatically; EditForm takes required, and form.SubmitButton takes disabled. Secret fields use secretInput (autocomplete="new-password" and the password managers' ignore attributes), since a filled-in password would replace the saved secret, and model IDs, handles, base URLs, and slugs use literalInput, so phone keyboards don't capitalize or correct them; both live in shared/ui/controls.tsx. Removing a role member asks to confirm in place. Add user, Add member, Add model, and Create role return their mutation from onSubmit, so the button stays busy until it finishes and can't send twice, and a listed model can't be ticked twice while it is being added. Every settings and admin route has a staticData title, and pageTitle joins them deepest first, as "API keys · Settings · SCN Chat", with a page opened from a section taking that section's title.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
0f93631c
Copied
Failed
Copy SHA
The line under a reply that is waiting, such as "The model's provider is busy. Trying again…", "In line", or "Lost the live stream. Checking for the reply…", is in medium weight and breathes as the model's name does while the reply waits, so it reads as something still going. statusLine in Status.tsx carries both, and holds still for reduced motion.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
acbaf8da
Copied
Failed
Copy SHA
Release 0.2.0
v0.2.0
Copy SHA
Browse repository
committed
1w ago
pouriade.com
ae0e6276
Copied
Failed
Copy SHA
The app has one version, the root package.json's, now 0.1.1 to match the latest release, and CHANGELOG.md records what each release changed, with what has landed since 0.1.1 under Unreleased. The workspace packages under apps/ and packages/ drop the 0.1.0 they never kept up, while plugins keep their own versions, which the admin's plugin list shows. pnpm release <version> cuts a release: it dates Unreleased, bumps the version, commits, makes an annotated tag, pushes, and publishes the GitHub Release with that version's section as its notes, refusing unless main is clean and current and Unreleased holds something. docs/releasing.md has the versioning rules and the steps, and CLAUDE.md says to add a changelog line with each change someone would notice.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
c2e806aa
Copied
Failed
Copy SHA
A thought, a step, or a reply's sources close without settling short and then dropping. The margins at the edges of their content collapsed out through the part that unfolds, so a closing one eased down to its summary and those margins, held there, and lost them at once when the content was hidden. The content is now its own formatting context, so its margins fold away with it.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
bea2e22b
Copied
Failed
Copy SHA
A file chip's kind and size, under its name, are in medium weight like the name, as a chip's label is and as the sources chip's are, rather than the regular weight of the details under a list item's name, so only their grey sets the two lines apart. The weight is set once for both lines, and the line no longer repeats the chip's text size. The styling spec says so.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
8f996f77
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
fe4fd701
Copied
Failed
Copy SHA
Update README.md
Copy SHA
Browse repository
committed
1w ago
pouriade.com
80836225
Copied
Failed
Copy SHA
A finished reply's steps open onto a quiet rail rather than a stack of tinted cards with large numbered circles. Each step has a light dot, green when its tool finished, red when it failed, and grey for a thought, joined to the next by a short soft segment that stops shy of both dots. Each row draws its segment down to the next dot, so the rail follows a step that is open. A failed step still says so in its badge. The live steps of a streaming reply keep their cards. The web UI spec says so.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
ac48921f
Copied
Failed
Copy SHA
Merge branch 'main' of https://github.com/Z-Space-Society/scn-chat
Copy SHA
Browse repository
committed
1w ago
pouriade.com
802c7c09
Copied
Failed
Copy SHA
A finished reply's steps button names its steps in medium weight, like the sources chip above it, rather than the regular weight that read too thin beside the action icons.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
3d0e2b09
Copied
Failed
Copy SHA
A reply's sources chip keeps as much room below it as above, so it no longer crowds the actions under it. The gap below was the actions' own small margin, which a smaller margin on the sources would only have merged into.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
3cc887ef
Copied
Failed
Copy SHA
A reply's sources count is a grey chip shaped like the source rows it opens onto, with their corners, padding, and hover, rather than loose muted text, so it reads as something to press. It carries a link icon before the count, a new puffy link drawn to sit with the logo's bubble letters, and its label is medium weight like the source titles. The web UI spec says so.
Copy SHA
Browse repository
committed
1w ago
hadsie.com
55e72238
Copied
Failed
Copy SHA
Update documentation.
Copy SHA
Browse repository
committed
1w ago
hadsie.com
ad696204
Copied
Failed
Copy SHA
Merge pull request #12 from
Z-Space-Society/rate-limits
Z-Space-Society/rate-limits
Copy SHA
Browse repository
committed
1w ago
hadsie.com
dead7601
Copied
Failed
Copy SHA
Retry busy providers. Don't retry spent keys. Add messaging for users.
rate-limits
Copy SHA
Browse repository
committed
1w ago
pouriade.com
20e80e3d
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
53fb89d2
Copied
Failed
Copy SHA
A footnote link scrolls to its note in the same reply rather than opening the page in a new tab, and the note's way back does the same. Every reply numbers its footnotes from one, so their ids repeat down the page and a plain jump would land in the first reply's notes; the link looks for its target inside its own reply instead. Streamdown's hardening gives every link a new tab, so a link within the text drops it. Other links still open in a new tab. The web UI spec says so, and the Markdown test renders two replies with the same footnote and checks the jump stays in the one clicked.
v0.1.1
Copy SHA
Browse repository
committed
1w ago
pouriade.com
0ba302fe
Copied
Failed
Copy SHA
Selected text is a calm blue, its own selection token, rather than the pale amber it shared with search matches and the Stopped badge. Blue is the one hue nothing else in the app means anything by, so a selection never reads as a match, a notice, or the green of an action. Text keeps its own ink on it: body text clears 7:1 and muted text 4.5:1 in both modes.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
e7b80fba
Copied
Failed
Copy SHA
A stopped reply says so with a small amber Stopped badge, carrying the Stop button's square, under where it broke off, rather than a loose line of muted text that read like the end of the reply. The badge is the shared Badge with a new warning tone, from the highlight tokens, so it stands apart from the grey source chips. The web UI spec says so, and the message test looks for the badge.
v0.1.0
Copy SHA
Browse repository
committed
1w ago
pouriade.com
a9135322
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
cc3e6a1d
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
31752f8c
Copied
Failed
Copy SHA
The sidebar's chat rows and account no longer render with every change to the list. The account menu was given its sync dot and status as elements built on the sync mutation, and sign-out as a function calling the sign-out mutation, and a mutation is a new object on every render, so each change to the chats rendered the account menu, card, and avatar again, seven times on a send. The account is now its own component holding its sync, given only a sign-out function that stays the same. Deleting a chat checked whether it was the open one through a callback React Compiler kept on the router's state, which changes on every navigation, so opening a chat rendered every row again. It now asks the router through a helper outside the component, so the callback stays the same. Checked in the running app: opening another chat renders no row and no account, and a send renders only the open chat's row and the sync dot.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
c10c6fcd
Copied
Failed
Copy SHA
Older messages no longer render again on send. The Try again callback every message's row gets called chat.regenerate as a method, so React Compiler kept it on the whole chat object, which changes with every update, and every row ran again three times per send, though each then reused its message. ConversationView now takes regenerate, regenerating, and stop out of chat first, so the callbacks stay the same. Checked in the running app over a send and its reply in an 18-message chat: only the new message and the reply rendered.
Copy SHA
Browse repository
committed
1w ago
pouriade.com
88da0e13
Copied
Failed
Copy SHA
A finished step's start time is forgotten once its duration is recorded, since the duration is all it needs from then on, so the step clock no longer keeps every start seen in the session. A reply's waiting header counts its wait with a timer that wakes just after each whole second since the reply was asked for, rather than checking four times a second, and a test checks it counts a second at a time with one timer.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
60fa9dff
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
86edb867
Copied
Failed
Copy SHA
Once Shiki and a language's grammar have loaded, a code block in that language is colored from its first frame, rather than drawn plain and laid out again in color a moment later, as every block was when a reply's record replaced its stream or a chat opened. The loaded highlighter is kept, so a block highlights while it first renders and in its layout effect, before the browser paints, and only the first block of a session waits for the import. The web UI spec says so, and a test checks a block is colored on its first render once its language has loaded.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
171fb03a
Copied
Failed
Copy SHA
A change to the open conversation, such as a message arriving or a reply finishing, renders again only the messages it changed, rather than every message and its actions. BranchView gives each message's row only its record, which the local copy keeps the same object while unchanged, its place among its versions as numbers and the rkeys on either side, and functions that stay the same from render to render: useChat's stop and regenerate call the mutations' own stable functions, and useModels gives the same empty list until models arrive. The reply running at the end of the branch, whose Stop the composer offers instead, reaches the Stop actions through a RunningReply context rather than the operations every action gets, so a reply starting or finishing rerenders only those. The web UI spec says so, and a ConversationView test counts each message's renders as a reply finishes.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
b78e2fb0
Copied
Failed
Copy SHA
A reply's stream is reduced in batches of the events that arrive within one animation frame, so a reply streaming a word at a time renders, and Streamdown completes and splits its whole text, at most once a frame rather than once a word. The status event is passed on at once with whatever came before it, and a dropped or unreadable stream still keeps every event that arrived first. A thought or tool step renders its body only once it is first opened, and keeps it once closed, so a closed thought no longer parses every token streaming into it and a closed tool step no longer holds all the tool returned. The body still unfolds, since it arrives while the step grows from nothing. The web UI spec and the architecture overview say so, the stream tests cover the batches, and the tool view tests open each step before reading it.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
5d56011a
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
e2411fa6
Copied
Failed
Copy SHA
Sign in's Continue reads "Signing in…" once it is clicked, and its arrow gives way to the button's spinner, since it is leaving for the PDS rather than waiting on something it was asked to do. Coming back with the browser's back button shows Continue and its arrow again. The web UI spec makes it the one busy button that changes its label, and the test checks the new label and that the spinner is its only icon.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
8350b775
Copied
Failed
Copy SHA
The UI and code fonts are each a native stack that covers every platform, so the app loads no web fonts. The sans drops ui-sans-serif, which only Safari reads, and "SF Pro Text", which system-ui already reaches, names Helvetica and Arial for Linux, where they map to lookalikes, and ends with the emoji fonts of each platform after sans-serif, Noto Color Emoji among them for Linux and Android. The mono gives SF Mono in Safari and on iOS through ui-monospace, Menlo in Chrome and Firefox on a Mac, which can't reach SF Mono, Cascadia Mono or Consolas on Windows, and Liberation Mono or DejaVu Sans Mono on Linux, and no longer names JetBrains Mono, which is only there when someone installed it.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
aff65526
Copied
Failed
Copy SHA
The docs catch up with the last week's changes. The architecture overview says a local server moves to the next free port when its own is taken, and a deployment's never does, that features reach the browser's local copy only through the store's hooks, with the open conversation as useChat and the reply follower showing a reply's stream until its record arrives, that the send route calls the runner's send with the request convention in turns/request.ts, that the pending reply is claimed at <key>.r<attempt> with a create-only call and streams over server-sent events with nothing more written meanwhile, and that each user's ChatService sits over their record and blob stores, with createChatServices the one place that picks the backend. The deployment guide sets NODE_ENV to production, leaves OAUTH_SCOPE_MODE unset since it defaults to permission-set, and mentions pnpm preview. The plugin guide says a secret that can't be decrypted reads as unset, and that options can be secret too. .env.example shows OAUTH_SCOPE_MODE commented out under its comment, which had lost its variable.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
34fd2ed0
Copied
Failed
Copy SHA
When the app runs on this machine and its port is taken, the server moves to the next free one, trying up to twenty, and PUBLIC_URL with it, since the OAuth redirect and the app's links name that address, and logs a warning saying so. It moves only when PUBLIC_URL is loopback on that same port, so a deployment, or a loopback URL that names another port, keeps its port and fails to start as before, since a reverse proxy points at it. A port counts as taken when it is at every address or at 127.0.0.1 or ::1 alone, since macOS lets a listener on every address share a port with one on 127.0.0.1, which then still gets the requests for http://127.0.0.1:3000. The tunnel picks the free port itself and points both ssh and the server at it. The foundation spec and .env.example describe it, and the tests cover the probe and when the environment moves.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
ba702282
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
83a45b30
Copied
Failed
Copy SHA
Update .env.example
Copy SHA
Browse repository
committed
2w ago
pouriade.com
02d2d188
Copied
Failed
Copy SHA
The overrides that pin the atproto alpha packages, and the dependencies allowed to run build scripts, live in pnpm-workspace.yaml rather than the "pnpm" field of package.json, which pnpm 11 no longer reads and warns about on every command. onlyBuiltDependencies becomes allowBuilds, which pnpm 10.33 also reads and turns back into the same allowlist, so the pinned pnpm and a global pnpm 11 install the same tree, and the frozen lockfile is unchanged. CLAUDE.md and the foundation spec say where the overrides now live.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
9a802e02
Copied
Failed
Copy SHA
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.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
fc2c1b88
Copied
Failed
Copy SHA
A search result's snippet of a reply renders its Markdown through Streamdown, as the reply itself does, so the web app no longer depends on react-markdown or remark-gfm. It uses Streamdown's Block rather than the Streamdown component, which would wrap the snippet's run of text in a div and complete the Markdown the snippet's cut left open, so the snippet renders the same elements as before and the tests pass unchanged. The plugin that marks matches is a named function, since Streamdown caches its parsers by the names of their plugins.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
867d1095
Copied
Failed
Copy SHA
A reply streams at an even pace, a word at a time, or a character at a time for Chinese and Japanese, where before it arrived in whatever bursts the provider sent. The turn's streamText uses the AI SDK's smoothStream with a chunking pattern of our own, since the SDK's word chunking waits for a space and would hold back Chinese and Japanese until a line ended. It waits 10 ms after each piece of text and reasoning alike, which caps a reply at about 100 words a second, so a faster model's record is written once smoothing catches up, within the turn's timeout. OpenAI-compatible models now show reasoning a server leaves in the reply inside <think> tags, such as llama.cpp with --reasoning-format none, as reasoning rather than as part of the answer, through the AI SDK's extractReasoningMiddleware, so that plugin now depends on the ai package. Its new startWithReasoning option is for models such as DeepSeek R1 whose template opens the tag in the prompt, so a reply has only the closing tag. The titles plugin refuses output that stopped at its output limit, such as reasoning that never reached its closing tag. The chat turns, providers, and titles specs describe it, along with the reasoning middleware dropping a reply's last characters when they could start a tag, which is a bug in the AI SDK, and docs/plugins.md says when a provider needs the ai package. Tests cover the smoothing, reasoning in think tags with and without the opening tag, whether streamed or not, and the cut-off title.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
d210fc70
Copied
Failed
Copy SHA
A conversation on its way shows a few paragraphs of text lines, spaced as a reply's are, in place of one turn's bubble, name, and paragraph, which read as a chat with one message rather than messages loading. MessagesSkeleton replaces TurnSkeleton, and the shared page shows it under its title too. The page's frame, which the server sends, now holds the conversation header's row with a line for its title, so the lines start where they will once the conversation loads, rather than under the sidebar's toggle on a narrow screen. The header's row and its title on its way live in HeaderFrame.tsx, which the header and the frame share, so the frame stays free of the header's panels and menus. The web UI spec describes it.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
63773430
Copied
Failed
Copy SHA
Update README.md
Copy SHA
Browse repository
committed
2w ago
pouriade.com
c8dfa1a3
Copied
Failed
Copy SHA
pnpm dev:remote and pnpm preview:remote run the dev server or the production build behind a localhost.run tunnel, for testing from a phone or another device. tunnel-cli.ts opens the tunnel over ssh, which needs no account and nothing to install, and starts the server with the tunnel's HTTPS URL as PUBLIC_URL for that run alone, leaving .env as it is, and Vite allowing the tunnel's host. Closing it closes the tunnel, and the tunnel closing stops the server. localhost.run because it streams a reply as it is written and shows no warning page in front of sign-in, where a Cloudflare quick tunnel holds back the stream until the reply ends, and Pinggy and serveo put a warning page in front of the app. Its free tier is slow, so a first load takes a while. The README, CLAUDE.md, and the foundation spec's scripts describe it.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
2ee2fcb6
Copied
Failed
Copy SHA
pnpm preview builds the web app and runs the production build on this machine, at http://127.0.0.1:3000, where before production refused any PUBLIC_URL that wasn't HTTPS. A loopback PUBLIC_URL now passes in production, and needs no OAUTH_PRIVATE_KEYS, since sign-in there uses the loopback client, as in development. Any other plain-HTTP URL is still refused. pnpm start stays the command a deployment runs, serving the build it was given. The README, the foundation spec's scripts, and the auth and admin settings specs describe it, with a test.
Copy SHA
Browse repository
committed
2w ago
pouriade.com
7d258d4c
Copied
Failed
Copy SHA
useChat, in features/conversation/chat.ts, is the open conversation as a chat, in the shape of the AI SDK's useChat without a message list of its own: the branch on screen and the messages behind it from the browser store, whether they are on their way or known empty, the reply running at the end of the branch, and send, regenerate, and stop, on top of useConversation, the reply follower, and useBranch. Every turn, from a send, an edit, or a regeneration, follows its reply, puts its new message on the branch, replacing m, and calls the view's onTurnStarted, with which ConversationView scrolls to the bottom, so regenerating now scrolls there too, as sending and editing did. useStartChat sends a new chat's first message, creating the chat and reusing it after a failed send. The composer only builds the message and hands it to send, so it no longer knows the chat's key, creates chats, or posts, and BranchComposer and NewConversation pass send through. useBranch absorbs useScrollToFocus, scrolling to a message the URL focuses while a picked one keeps the scroll, so the shared page now scrolls to a linked message too. useConversationActions keeps renaming, the instructions, and syncing, and the conversation's one alert shows whichever action or turn ran last. Its tests cover sending, regenerating, stopping, the running reply, starting a chat, and scrolling to a focus, and the composer's tests check the message it hands to send. The web UI spec describes the chat.
Copy SHA
Browse repository
committed
2w ago
Previous
1
2
3
4
Next