diff --git a/CHANGELOG.md b/CHANGELOG.md index e463677..5426872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ What changed in each release of SCN Chat, for the people who use it, run it, or ### Changed - The server now works with PDSs on the 2026-10-22 spaces alpha, and no longer with older spaces PDSs. On an updated PDS, the chat list failed to load with `Missing required key "hash"`. Opening a shared chat also needs the owner's new `#atproto_kem` key in their DID document. An updated PDS publishes one for the did:plc accounts it manages, and a did:web account has to add its own. +- The login page and shared chats download about 15 KB less, compressed, before they show, since the router's loading page no longer brings the settings form kit with it. For forks: `SettingsPageSkeleton` and `SettingRowSkeleton` moved to `shared/ui/SettingsPageSkeleton.tsx`, and `dividedRows` and `undivided` to `shared/ui/divided-rows.ts`. ### Fixed diff --git a/apps/web/src/features/admin/components/ProviderModels.tsx b/apps/web/src/features/admin/components/ProviderModels.tsx index 8ee2232..c2e6c61 100644 --- a/apps/web/src/features/admin/components/ProviderModels.tsx +++ b/apps/web/src/features/admin/components/ProviderModels.tsx @@ -8,13 +8,13 @@ import { ErrorAlert } from '../../../shared/ui/Alert.tsx' import { Badge } from '../../../shared/ui/Badge.tsx' import { Button } from '../../../shared/ui/Button.tsx' import { Checklist, ChecklistItem } from '../../../shared/ui/Checklist.tsx' +import { undivided } from '../../../shared/ui/divided-rows.ts' import * as ItemList from '../../../shared/ui/ItemList.tsx' import { inlineForm, SettingRow, SettingsEmpty, SettingsGroup, - undivided, } from '../../../shared/ui/SettingsPage.tsx' import { ToggleChip } from '../../../shared/ui/ToggleChip.tsx' import { type Capabilities, capabilities } from '../../models/capabilities.ts' diff --git a/apps/web/src/features/admin/pages/UsersAdmin.tsx b/apps/web/src/features/admin/pages/UsersAdmin.tsx index 8cd8b79..58017eb 100644 --- a/apps/web/src/features/admin/pages/UsersAdmin.tsx +++ b/apps/web/src/features/admin/pages/UsersAdmin.tsx @@ -2,13 +2,9 @@ import { useSuspenseInfiniteQuery, useSuspenseQuery } from '@tanstack/react-quer import { Suspense } from 'react' import { ErrorAlert } from '../../../shared/ui/Alert.tsx' import { Button } from '../../../shared/ui/Button.tsx' +import { undivided } from '../../../shared/ui/divided-rows.ts' import * as ItemList from '../../../shared/ui/ItemList.tsx' -import { - SettingsEmpty, - SettingsGroup, - SettingsPage, - undivided, -} from '../../../shared/ui/SettingsPage.tsx' +import { SettingsEmpty, SettingsGroup, SettingsPage } from '../../../shared/ui/SettingsPage.tsx' import { Invites } from '../components/Invites.tsx' import { UserRow } from '../components/UserRow.tsx' import { UserSearch } from '../components/UserSearch.tsx' diff --git a/apps/web/src/features/settings/pages/ApiKeyModelSettings.tsx b/apps/web/src/features/settings/pages/ApiKeyModelSettings.tsx index 213079a..084accb 100644 --- a/apps/web/src/features/settings/pages/ApiKeyModelSettings.tsx +++ b/apps/web/src/features/settings/pages/ApiKeyModelSettings.tsx @@ -9,6 +9,7 @@ import { ErrorAlert } from '../../../shared/ui/Alert.tsx' import { Button, IconButton } from '../../../shared/ui/Button.tsx' import { Checklist, ChecklistItem } from '../../../shared/ui/Checklist.tsx' import { Input } from '../../../shared/ui/controls.tsx' +import { undivided } from '../../../shared/ui/divided-rows.ts' import * as ItemList from '../../../shared/ui/ItemList.tsx' import { backLink, @@ -21,7 +22,6 @@ import { SettingsEmpty, SettingsGroup, SettingsPage, - undivided, } from '../../../shared/ui/SettingsPage.tsx' import { ToggleChip } from '../../../shared/ui/ToggleChip.tsx' import { type Capabilities, capabilities, noCapabilities } from '../../models/capabilities.ts' diff --git a/apps/web/src/features/settings/pages/SyncSettings.tsx b/apps/web/src/features/settings/pages/SyncSettings.tsx index 3f2c3b3..d330763 100644 --- a/apps/web/src/features/settings/pages/SyncSettings.tsx +++ b/apps/web/src/features/settings/pages/SyncSettings.tsx @@ -8,10 +8,10 @@ import { Callout, ConfirmButton, SettingRow, - SettingRowSkeleton, SettingsGroup, SettingsPage, } from '../../../shared/ui/SettingsPage.tsx' +import { SettingRowSkeleton } from '../../../shared/ui/SettingsPageSkeleton.tsx' import { useOpenStore } from '../../../store/react.tsx' import { useMe } from '../../auth/session.ts' import { accountQuery } from '../queries.ts' diff --git a/apps/web/src/shared/route-states.tsx b/apps/web/src/shared/route-states.tsx index 2e8440f..3ee0dcd 100644 --- a/apps/web/src/shared/route-states.tsx +++ b/apps/web/src/shared/route-states.tsx @@ -3,7 +3,7 @@ import { type ErrorComponentProps, useRouter } from '@tanstack/react-router' import { useEffect } from 'react' import { ErrorAlert } from './ui/Alert.tsx' import { Button } from './ui/Button.tsx' -import { SettingsPageSkeleton } from './ui/SettingsPage.tsx' +import { SettingsPageSkeleton } from './ui/SettingsPageSkeleton.tsx' /** * What a route shows when its loader or a query it suspends on fails, in place of the route, so diff --git a/apps/web/src/shared/schema-fields/SchemaFields.tsx b/apps/web/src/shared/schema-fields/SchemaFields.tsx index 95d1647..027c012 100644 --- a/apps/web/src/shared/schema-fields/SchemaFields.tsx +++ b/apps/web/src/shared/schema-fields/SchemaFields.tsx @@ -4,8 +4,9 @@ import { splitLines } from '../lines.ts' import type { Issue } from '../response.ts' import { Alert } from '../ui/Alert.tsx' import { Input, Switch, TextArea } from '../ui/controls.tsx' +import { dividedRows } from '../ui/divided-rows.ts' import { Select } from '../ui/Select.tsx' -import { dividedRows, SettingRow } from '../ui/SettingsPage.tsx' +import { SettingRow } from '../ui/SettingsPage.tsx' import { applyRule, type FieldRule } from './field-rules.ts' export type Property = { diff --git a/apps/web/src/shared/ui/SettingsPage.tsx b/apps/web/src/shared/ui/SettingsPage.tsx index a4d5e5f..28d08d4 100644 --- a/apps/web/src/shared/ui/SettingsPage.tsx +++ b/apps/web/src/shared/ui/SettingsPage.tsx @@ -7,8 +7,8 @@ import { CheckIcon } from '../icons.tsx' import { ErrorAlert } from './Alert.tsx' import { Button, IconButton } from './Button.tsx' import { Radio } from './controls.tsx' +import { dividedRows } from './divided-rows.ts' import { FieldDescriptionContext } from './field-description.ts' -import { Loading, Skeleton, SkeletonText } from './Skeleton.tsx' /* * The pieces settings and admin pages are built from: a page of cards, each holding rows of @@ -96,16 +96,9 @@ interface CardProps { children: ReactNode } -/** What a card holds after something it continues, such as a search's results, with no line between. */ -export const undivided = 'before:hidden' - /** A form of one field and its button across a card, such as a search or adding a person. */ export const inlineForm = 'flex gap-2 p-card-pad [&>input]:min-w-0 [&>input]:flex-1' -/** Rows divided by lines inset to their text, for a card's rows or a set of rows within one. */ -export const dividedRows = - '[&>*+*]:relative [&>*+*]:before:absolute [&>*+*]:before:inset-x-card-pad [&>*+*]:before:top-0 [&>*+*]:before:border-line [&>*+*]:before:border-t' - /** group/card lets what a card holds line up with the rest of it, as number fields do by their units. */ function SettingsCard(props: CardProps) { return ( @@ -535,59 +528,3 @@ export function Callout(props: CalloutProps) { ) } - -/** The widths of a page skeleton's rows, a setting's name over what it does, so they differ. */ -const skeletonRows = [ - { name: 'w-1/3', about: 'w-3/5' }, - { name: 'w-1/4', about: 'w-1/2' }, - { name: 'w-2/5', about: 'w-2/3' }, -] - -/** - * A settings page while it loads, in the page's own shape: its title and what it is for, over a - * card of rows, each a setting's name and description beside its control. It is a route's pending - * page, which the router already holds back until loading takes a while, so it shows at once. - */ -export function SettingsPageSkeleton() { - return ( - -
- - -
- -
- {skeletonRows.map((row) => ( - - ))} -
-
- ) -} - -/** A setting's row on its way, in a card of settings that has loaded, as one waiting on its own data. */ -export function SettingRowSkeleton() { - return ( - - - - ) -} - -interface ShapeProps { - /** The widths of the setting's name and of what it does. */ - name: string - about: string -} - -function SettingRowShape(props: ShapeProps) { - return ( -
-
- - -
- -
- ) -} diff --git a/apps/web/src/shared/ui/SettingsPageSkeleton.tsx b/apps/web/src/shared/ui/SettingsPageSkeleton.tsx new file mode 100644 index 0000000..ad27619 --- /dev/null +++ b/apps/web/src/shared/ui/SettingsPageSkeleton.tsx @@ -0,0 +1,65 @@ +import { cn } from '../cn.ts' +import { dividedRows } from './divided-rows.ts' +import { Loading, Skeleton, SkeletonText } from './Skeleton.tsx' + +/* + * Settings and admin pages while they load, apart from `SettingsPage.tsx`, which imports the form + * kit, so the router's pending page doesn't add the form kit to the code every page downloads + * first. + */ + +/** The widths of a page skeleton's rows, a setting's name over what it does, so they differ. */ +const skeletonRows = [ + { name: 'w-1/3', about: 'w-3/5' }, + { name: 'w-1/4', about: 'w-1/2' }, + { name: 'w-2/5', about: 'w-2/3' }, +] + +/** + * A settings page while it loads, in the page's own shape: its title and what it is for, over a + * card of rows, each a setting's name and description beside its control. It is a route's pending + * page, which the router already holds back until loading takes a while, so it shows at once. + */ +export function SettingsPageSkeleton() { + return ( + +
+ + +
+ +
+ {skeletonRows.map((row) => ( + + ))} +
+
+ ) +} + +/** A setting's row on its way, in a card of settings that has loaded, as one waiting on its own data. */ +export function SettingRowSkeleton() { + return ( + + + + ) +} + +interface ShapeProps { + /** The widths of the setting's name and of what it does. */ + name: string + about: string +} + +function SettingRowShape(props: ShapeProps) { + return ( +
+
+ + +
+ +
+ ) +} diff --git a/apps/web/src/shared/ui/divided-rows.ts b/apps/web/src/shared/ui/divided-rows.ts new file mode 100644 index 0000000..4e5b77d --- /dev/null +++ b/apps/web/src/shared/ui/divided-rows.ts @@ -0,0 +1,6 @@ +/** Rows divided by lines inset to their text, for a card's rows or a set of rows within one. */ +export const dividedRows = + '[&>*+*]:relative [&>*+*]:before:absolute [&>*+*]:before:inset-x-card-pad [&>*+*]:before:top-0 [&>*+*]:before:border-line [&>*+*]:before:border-t' + +/** What a card holds after something it continues, such as a search's results, with no line between. */ +export const undivided = 'before:hidden' diff --git a/specs/styling.md b/specs/styling.md index 2da0e25..e9bc5d4 100644 --- a/specs/styling.md +++ b/specs/styling.md @@ -104,10 +104,11 @@ Spacing literals from `design-reference` snap to the nearest quarter step of the - `CopyButton.tsx`: Copy, as words or as an icon that cross-fades to a check. - `ItemList.tsx`: lists in a card, imported as `import * as ItemList` and composed from its parts: `Root`, each `Item` with a `Mark`, its `Names` over a line of details, and its actions, and `LinkItem`, a row of the same parts that opens the page its title links to. - `SettingsPage.tsx`: the settings and admin kit of `SettingsPage`, `SettingsGroup`, `SettingRow`, `Field` and the `fields` grid, `Choices` and `Choice`, `SaveBar` and `AddBar`, `ConfirmButton`, `SettingsEmpty`, and `Callout`, a note in the page's flow, which is an alert when it is red. A settings page is a size container, so its rows stack when its column is narrow, beside the nav as well as on a phone. +- `divided-rows.ts`: `dividedRows`, the lines inset to their text between a card's rows, and `undivided`, which drops the line above what continues the row before it, such as a search's results. Apart from `SettingsPage.tsx`, so the skeletons draw the lines without the form kit. - `Logo.tsx`: the logo. -- `Skeleton.tsx`: `Skeleton`, a pulsing shape in `--color-skeleton`, `SkeletonText`, a line as tall as the text around it, and `Loading`, which reads its skeletons out once as loading and fades them in only after `--wait-quiet`, so a quick load never flashes them. A route's pending page shows its skeletons at once, since the router already holds it back for the same 400ms (`defaultPendingMs`) and keeps it at least 300ms (`defaultPendingMinMs`), and so does a page the server sends. Data a route needs is read with `useSuspenseQuery` after its loader starts it, so the route's pending page and error page cover it. `useQuery` with its `isLoading` or `isPending` is kept for what loads inside a page that has already shown: the browser store's reads, which wait on this tab holding the store, the models, which never hold up sending, and optional data such as the profile. The settings kit has `SettingsPageSkeleton` and `SettingRowSkeleton`, and `ItemList.tsx` has `RowsSkeleton`. +- `Skeleton.tsx`: `Skeleton`, a pulsing shape in `--color-skeleton`, `SkeletonText`, a line as tall as the text around it, and `Loading`, which reads its skeletons out once as loading and fades them in only after `--wait-quiet`, so a quick load never flashes them. A route's pending page shows its skeletons at once, since the router already holds it back for the same 400ms (`defaultPendingMs`) and keeps it at least 300ms (`defaultPendingMinMs`), and so does a page the server sends. Data a route needs is read with `useSuspenseQuery` after its loader starts it, so the route's pending page and error page cover it. `useQuery` with its `isLoading` or `isPending` is kept for what loads inside a page that has already shown: the browser store's reads, which wait on this tab holding the store, the models, which never hold up sending, and optional data such as the profile. The settings kit's skeletons, `SettingsPageSkeleton` and `SettingRowSkeleton`, are in `SettingsPageSkeleton.tsx`, and `ItemList.tsx` has `RowsSkeleton`. -The form kit in `src/shared/form.tsx` binds `Input`, `TextArea`, and `Select` as its text, textarea, and select fields, binds a boolean to a `SwitchField`, and submits with `Button`. The router's `RouteError` and `RoutePending` use the kit. `RoutePending` is a settings page's skeleton, since only settings and admin pages load before they show. +The form kit in `src/shared/form.tsx` binds `Input`, `TextArea`, and `Select` as its text, textarea, and select fields, binds a boolean to a `SwitchField`, and submits with `Button`. The router's `RouteError` and `RoutePending`, in `src/shared/route-states.tsx`, are what a route shows when it fails or while it loads. `RoutePending` is a settings page's skeleton, since only settings and admin pages load before they show. It comes from `SettingsPageSkeleton.tsx` rather than `SettingsPage.tsx`, which imports the form kit, since every page's code includes the router's, and the login and shared chat pages have no form. ### Light and dark