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
/
shared
/
ui
21 files
at
admin-config-export
pouriade.com
·
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.
·
1w ago
3c7e3ad4
actions.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
Badge.tsx
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.
2w ago
Button.tsx
Content on its way shows a skeleton in its own shape, pulsing gently on a new --color-skeleton, in place of every "Loading…" and every empty space that filled in later. Loading reads its shapes out once and fades them in after the quiet wait, while a route's pending page shows at once, since the router now holds it back 400ms and keeps it at least 300ms. The chat frame has the sidebar's logo and toggle over New chat, the chats, and the account card; the chat list keeps its rows until the local copy answers or, with no chats yet, until the index first syncs; a search shows rows until it first answers rather than saying nothing matched; the account card holds its avatar and name; a conversation holds its title and first turn, so a titled chat never flashes New chat; the model selects hold a line for the name, so a chosen model no longer flashes as unavailable; and settings and admin pages, the users list, the Sync page's switch, the Share panel, and the shared page each have theirs. The store hooks report loading as React Query's isLoading, or the store still opening in this tab, so no component checks the handover. The shared page's loader fetches the chat for a signed-in viewer and the page reads it with useSuspenseQuery, under the route's skeleton and a centered error page with Retry. A busy button keeps its label with a turning spinner before it, a little stronger than a disabled one, for Save, every submit, Load models, Rebuild, Sign in's Continue, and the rest.
2w ago
Checklist.tsx
The OpenAI-compatible plugin's model list sends sessionHeader too, with a fresh session ID shaped like a conversation's, since a list belongs to no conversation, and userAgent's hint and the spec say it leads the user agent, with the AI SDK's name after it on chat calls. Unticking a loaded model on a key's page takes it out of the draft, which the spec now says, along with the page's notes for a key that can't be read or isn't saved, any route answering 400 for an undecryptable key, and the Select scrolling to its active option only when it changes, each with a test and an acceptance criterion. ModelName shows a model's ID only when it differs from its name, in both the admin's and a key's list of models to tick, the admin's model rows name capabilities by their labels from the shared list, noCapabilities is typed by it, and decryptKey refuses an unreadable key in one place.
2w ago
controls.tsx
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.
2w ago
CopyButton.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
details.css
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.
2w ago
FileChip.tsx
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.
2w ago
ItemList.tsx
Each API key has its own page at /settings/api-keys/<id>, opened from its row, which the list now shows with how many models the key covers. Under a link back to the list, the page is titled with the key's name over its provider and base URL, and has a card for each model with Vision, Thinking, and Tools switches, the same as an admin model's, and one Save, so a user's own models can say what they can do. PATCH /api/credentials/:id replaces a key's models, refusing an empty list or a model listed twice, and leaves the key itself alone. The capability list moves to features/models for both pages to share, SectionNav takes an exact flag per section, and LinkItem fades a row on its way out.
2w ago
Logo.tsx
Port the login page from design-reference: the logo and app name over a tagline, a handle field with its @ inside, Continue that says it is redirecting to the PDS until the page is left, the server's error tied to the field, and a viewer's notice above the form with Sign out as a quiet link under it. The server drops a leading @ from the handle. Button gains a link tone, the theme an underline offset, and the logo is decoration when it has no name.
2w ago
menu.ts
Add the shared UI kit in src/shared/ui. Button with default, primary, danger, and quiet tones, primary when it submits, IconButton, Badge and Chips, the field look, Switch and Radio drawing their knob and dot beside the input, the Select from design-reference on usePlacement, the floating menu look, CopyButton, item and link lists, and the settings kit of page, group, row, field, choices, SaveBar, ConfirmButton, empty state, and callout, whose rows stack in a narrow column. The form kit's fields take the field look and gain SwitchField, and ErrorAlert, RouteError, and RoutePending use the kit. The theme gains letter spacings, the radio's edge, and the copy icon's swap, and the guard lets whole extents like 100vw through.
2w ago
MenuItem.tsx
Instructions in a chat's More menu opens a panel to edit the chat's own instructions, the systemPrompt of its info record, which the model follows in every reply there after the user's custom instructions. Saving sends them trimmed through the conversation's PATCH route and closes the panel, saving them unchanged only closes it, so the chat doesn't move up the list, and a failed save shows in the conversation's alert and keeps it open. Saving them blank removes systemPrompt from the record, as when the chat was created, since updateInfo now drops a field a patch sets to undefined. The menu item is disabled until the chat's info record has arrived from the local copy or the PDS, so the panel never starts empty over stored instructions, and the title waits for the same record, so a chat not yet on this device shows its skeleton rather than "New chat". Rename and instructions share one patch mutation, a disabled MenuItem shows as such, and the sharing panel's floating card is PanelCard, shared with the instructions panel, whose panelForm keeps a panel's form from taking room in the header's row, which had nudged its buttons aside. The web UI and styling specs describe it, with tests and an acceptance criterion.
2w ago
PanelCard.tsx
Instructions in a chat's More menu opens a panel to edit the chat's own instructions, the systemPrompt of its info record, which the model follows in every reply there after the user's custom instructions. Saving sends them trimmed through the conversation's PATCH route and closes the panel, saving them unchanged only closes it, so the chat doesn't move up the list, and a failed save shows in the conversation's alert and keeps it open. Saving them blank removes systemPrompt from the record, as when the chat was created, since updateInfo now drops a field a patch sets to undefined. The menu item is disabled until the chat's info record has arrived from the local copy or the PDS, so the panel never starts empty over stored instructions, and the title waits for the same record, so a chat not yet on this device shows its skeleton rather than "New chat". Rename and instructions share one patch mutation, a disabled MenuItem shows as such, and the sharing panel's floating card is PanelCard, shared with the instructions panel, whose panelForm keeps a panel's form from taking room in the header's row, which had nudged its buttons aside. The web UI and styling specs describe it, with tests and an acceptance criterion.
2w ago
scroll-fade.css
Port the chat sidebar from design-reference. The server answers GET /api/me/profile with the display name and Bluesky's CDN avatar thumbnail, read from the PDS on each request, and the browser store lists each chat's pending reply and newest complete reply and records when the index last synced. The sidebar puts New chat beside a search toggle, the tags as a folding filter, the chats under date headings with a dot for a reply in progress or one not yet seen on this device, a menu on each to rename, tag, or delete it in place, search results grouped by chat with their Markdown inline, and the account at the foot with a sync dot and a menu of where the chats live, their sync, Settings, Admin, and Sign out. New chat still creates the chat until the empty composer arrives.
2w ago
Secret.tsx
Port the admin area from design-reference. Users is a card to add a person over those waiting to sign in, then a search over a list of accounts, each with its initial, its DID, storage, and last activity, its roles as chips with a picker to add one, and its access at the end, where Suspend asks for a reason first. Roles and plugins are lists whose rows open their pages, which link back above their titles, a plugin's numbered in load order with its status and arrows to move it. A role's page holds its members, then its details with hosts and domains in a mono field. Access picks a registration mode from choices, with invite roles as chips that toggle. Models picks the default by a click anywhere on its row. A plugin's page holds its settings, a list of its provider's models, and a card to tick or type more. A model's capabilities are switches. Removing a role, plugin, or model, and revoking a key, confirm in place, and each sits in a red card. An issued key shows once in a green note with Copy. Cron shows the last run with a badge and the crontab line to copy. General, Turns, and Sync choose their cards per route, and the settings schemas gain clearer titles, descriptions, and units, which SchemaFields shows after a number.
2w ago
Select.tsx
A Select given searchLabel opens a list of eight options or more, about as many as it shows before scrolling, with a search box named by it, and the model select gives "Search models", which finds a model by its name, its ID, or its provider through the options' new keywords. The box takes the focus as the list opens, except on a touch screen, where it waits for a tap so its keyboard doesn't cover the list. Typing narrows the options to those holding every word typed, in any case, moves to the first that matches, and says "No matches" through a status that is there from the start, so screen readers hear it. The arrow keys, Page Up, Page Down, and Enter work from the box while Home, End, and Space stay the field's, Escape closes the list and Tab moves on, each from the button, which takes the focus back as after a pick, and a letter typed on the closed button opens the list searching for it. An option's id comes from its place among every option, so the active id changes with each move and screen readers and the scroll into view follow it. The list's heading and search box sit over the options, which alone scroll, and usePlacement measures the room the reader can see, following the visual viewport as an on-screen keyboard comes and goes. touchOnly is shared with the composer from shared/touch.ts. The styling and web UI specs describe it, with tests and an acceptance criterion.
2w ago
SettingsPage.tsx
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.
1w ago
sidebar.ts
Port the chat sidebar from design-reference. The server answers GET /api/me/profile with the display name and Bluesky's CDN avatar thumbnail, read from the PDS on each request, and the browser store lists each chat's pending reply and newest complete reply and records when the index last synced. The sidebar puts New chat beside a search toggle, the tags as a folding filter, the chats under date headings with a dot for a reply in progress or one not yet seen on this device, a menu on each to rename, tag, or delete it in place, search results grouped by chat with their Markdown inline, and the account at the foot with a sync dot and a menu of where the chats live, their sync, Settings, Admin, and Sign out. New chat still creates the chat until the empty composer arrives.
2w ago
SidebarToggle.tsx
Port the shell from design-reference. SidebarLayout takes the page's kind: the chat sidebar opens with the logo and a toggle that slides the column off the window's edge on a wide screen, kept in the browser, with a toggle in the panel to bring it back, and the settings and admin nav sit inside the panel beside the section. Below 720px either is a drawer over the page that closes on navigation. The settings and admin nav give each section an icon under a title and the way back, the admin's in People, Features, and App groups, with Sign out left to the chat sidebar. The root shows design-reference's error page, and the chats route sends ChatPageFrame while its code loads. The shell keeps the chats, settings, and sidebar classes as hooks for main's stand-in look until what it holds is ported.
2w ago
Skeleton.tsx
Content on its way shows a skeleton in its own shape, pulsing gently on a new --color-skeleton, in place of every "Loading…" and every empty space that filled in later. Loading reads its shapes out once and fades them in after the quiet wait, while a route's pending page shows at once, since the router now holds it back 400ms and keeps it at least 300ms. The chat frame has the sidebar's logo and toggle over New chat, the chats, and the account card; the chat list keeps its rows until the local copy answers or, with no chats yet, until the index first syncs; a search shows rows until it first answers rather than saying nothing matched; the account card holds its avatar and name; a conversation holds its title and first turn, so a titled chat never flashes New chat; the model selects hold a line for the name, so a chosen model no longer flashes as unavailable; and settings and admin pages, the users list, the Sync page's switch, the Share panel, and the shared page each have theirs. The store hooks report loading as React Query's isLoading, or the store still opening in this tab, so no component checks the handover. The shared page's loader fetches the chat for a signed-in viewer and the page reads it with useSuspenseQuery, under the route's skeleton and a centered error page with Retry. A busy button keeps its label with a turning spinner before it, a little stronger than a disabled one, for Save, every submit, Load models, Rebuild, Sign in's Continue, and the rest.
2w ago
ToggleChip.tsx
A key's page lists its models in one Models card, a row each with its name, its ID when that differs, Vision, Thinking, and Tools as chips that stay pressed while on, and a button that removes it. Under it, Add models has a field to type a model ID, where Enter adds rather than saves, and, for providers that list their models, Load models, which lists them with the saved key through the new POST /api/credentials/:id/list-models, to tick the ones to use. The changes are one draft under one Save, disabled while the key has no models. The route refuses a key that can't be decrypted with a 400 asking for it again. The store refuses an empty model list or a model listed twice for every save, adding a key included, and the app's error handler turns its credential errors into a 400. The ticked list of models is a shared Checklist, used by the admin's provider page too, ToggleChip is a shared component, and noCapabilities, a key's description line, and useKeysChanged, now in settings/hooks/changes.ts, each live in one place.
2w ago