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
/
server
/
tests
13 folders
·
9 files
at
rate-limits
hadsie.com
·
Retry busy providers. Don't retry spent keys. Add messaging for users.
·
2w ago
dead7601
admin
The app has a description, set in the admin area beside its name, which the login page shows under the name in place of its fixed tagline. Every page carries it as its meta description and in Open Graph tags for link previews, with robots noindex, since each page is someone's own. A page's title is its route's staticData.title, such as Settings, Admin, or Shared chat, before the app name. The login page is the one page search engines may list: it overrides robots and names its canonical URL and og:url under PUBLIC_URL, which the server now passes with each request. /robots.txt keeps crawlers out of /api, /oauth, and /xrpc. shared/app-name.ts becomes shared/site.ts, which reads the name and description from the rendered page in the browser.
2w ago
auth
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
blobs
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
db
Add the admin area for settings to replace config.yml.
4w ago
fixtures
Add the admin area for settings to replace config.yml.
4w ago
helpers
Retry busy providers. Don't retry spent keys. Add messaging for users.
2w ago
plugins
A secret the server can no longer decrypt, as after a lost SECRET_KEY, reads as unset and is flagged, rather than failing the page that reads it or, for an admin plugin's options, the server's startup. The settings page's web search, the admin plugin forms, and every other schema form say on each secret field that its saved value can't be read and to enter it again, and an admin can tick Clear to discard it. A BYO key that can't be read says so on the API keys page in place of its last four characters, found from its key ID without decrypting it, and a turn that uses it ends with an error asking for the key again. The unreadable value is kept until a new secret is entered, cleared, or reset, so restoring the old key in SECRET_KEYS_OLD brings it back. SecretBox gains tryDecrypt and hasKeyFor, and a value that fails authentication now throws SecretDecryptError too.
2w ago
providers
Retry busy providers. Don't retry spent keys. Add messaging for users.
2w ago
settings
The app has a description, set in the admin area beside its name, which the login page shows under the name in place of its fixed tagline. Every page carries it as its meta description and in Open Graph tags for link previews, with robots noindex, since each page is someone's own. A page's title is its route's staticData.title, such as Settings, Admin, or Shared chat, before the app name. The login page is the one page search engines may list: it overrides robots and names its canonical URL and og:url under PUBLIC_URL, which the server now passes with each request. /robots.txt keeps crawlers out of /api, /oauth, and /xrpc. shared/app-name.ts becomes shared/site.ts, which reads the name and description from the rendered page in the browser.
2w ago
sharing
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
storage
The conversation:created and conversation:deleted plugin hooks run once for each conversation created or deleted, whichever client made the change, where before only the app's own routes ran them. fireChatHooks, wired in server.ts, runs them from the in-process event bus. ChatService announces the conversations the app creates and deletes, through a listener in place of its onWrite callback. Sync announces one another client created when an index sync finds a new conversationRef, whose op has no previous CID, and one another client deleted through the engine's new conversationDeleted, which the deleted index entry, the missing space, and notifySpaceDeleted all go through. RecentWrites remembers the spaces the server deleted for ten minutes, so sync noticing the server's own deletion doesn't announce it again. The first index sync after sign-in, and any while the index is empty, announces nothing. The routes no longer wait for the hooks before answering, and open tabs now hear about deletions made through the app too. The plugins spec and guide describe it, with tests and an acceptance criterion.
2w ago
sync
The conversation:created and conversation:deleted plugin hooks run once for each conversation created or deleted, whichever client made the change, where before only the app's own routes ran them. fireChatHooks, wired in server.ts, runs them from the in-process event bus. ChatService announces the conversations the app creates and deletes, through a listener in place of its onWrite callback. Sync announces one another client created when an index sync finds a new conversationRef, whose op has no previous CID, and one another client deleted through the engine's new conversationDeleted, which the deleted index entry, the missing space, and notifySpaceDeleted all go through. RecentWrites remembers the spaces the server deleted for ten minutes, so sync noticing the server's own deletion doesn't announce it again. The first index sync after sign-in, and any while the index is empty, announces nothing. The routes no longer wait for the hooks before answering, and open tabs now hear about deletions made through the app too. The plugins spec and guide describe it, with tests and an acceptance criterion.
2w ago
turns
Retry busy providers. Don't retry spent keys. Add messaging for users.
2w ago
app.test.ts
The app has a description, set in the admin area beside its name, which the login page shows under the name in place of its fixed tagline. Every page carries it as its meta description and in Open Graph tags for link previews, with robots noindex, since each page is someone's own. A page's title is its route's staticData.title, such as Settings, Admin, or Shared chat, before the app name. The login page is the one page search engines may list: it overrides robots and names its canonical URL and og:url under PUBLIC_URL, which the server now passes with each request. /robots.txt keeps crawlers out of /api, /oauth, and /xrpc. shared/app-name.ts becomes shared/site.ts, which reads the name and description from the rendered page in the browser.
2w ago
config.test.ts
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.
2w ago
cron.test.ts
Add API key interface and a cron route. Improve the role membership hooks.
3w ago
lex-errors.test.ts
Add the admin area for settings to replace config.yml.
4w ago
logger.test.ts
Crash and privacy fixes, request validation, login CSRF, SSRF guard, handover race, and cleanup.
1mo ago
port.test.ts
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.
2w ago
secrets.test.ts
A secret the server can no longer decrypt, as after a lost SECRET_KEY, reads as unset and is flagged, rather than failing the page that reads it or, for an admin plugin's options, the server's startup. The settings page's web search, the admin plugin forms, and every other schema form say on each secret field that its saved value can't be read and to enter it again, and an admin can tick Clear to discard it. A BYO key that can't be read says so on the API keys page in place of its last four characters, found from its key ID without decrypting it, and a turn that uses it ends with an error asking for the key again. The unreadable value is kept until a new secret is entered, cleared, or reset, so restoring the old key in SECRET_KEYS_OLD brings it back. SecretBox gains tryDecrypt and hasKeyFor, and a value that fails authentication now throws SecretDecryptError too.
2w ago
server.test.ts
Add the admin area for settings to replace config.yml.
4w ago
web.test.ts
The app has a description, set in the admin area beside its name, which the login page shows under the name in place of its fixed tagline. Every page carries it as its meta description and in Open Graph tags for link previews, with robots noindex, since each page is someone's own. A page's title is its route's staticData.title, such as Settings, Admin, or Shared chat, before the app name. The login page is the one page search engines may list: it overrides robots and names its canonical URL and og:url under PUBLIC_URL, which the server now passes with each request. /robots.txt keeps crawlers out of /api, /oauth, and /xrpc. shared/app-name.ts becomes shared/site.ts, which reads the name and description from the rendered page in the browser.
2w ago