Add the account area and account-synced Appearance preferences
CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 50s
CI / migration-smoke (pull_request) Successful in 2m26s
CI / image-build (pull_request) Successful in 1m12s
CI / deploy-smoke (same-origin) (pull_request) Successful in 42s
CI / package-publication (pull_request) Successful in 54s
CI / deploy-smoke (split) (pull_request) Successful in 3m17s
CI / verify (pull_request) Successful in 1m10s
CI / infrastructure-smoke (pull_request) Successful in 34s
CI / package-release (pull_request) Successful in 50s
CI / migration-smoke (pull_request) Successful in 2m26s
CI / image-build (pull_request) Successful in 1m12s
CI / deploy-smoke (same-origin) (pull_request) Successful in 42s
CI / package-publication (pull_request) Successful in 54s
CI / deploy-smoke (split) (pull_request) Successful in 3m17s
Stage 1 PR 6 (ADR 0062). /account shows who is signed in; /account/appearance chooses System, Light, or Dark and reduced motion (System, Reduce, Allow). - Core: ekklesia_core_user_preference, one typed, CHECK-constrained, nullable column per preference (null = never chosen), migration 0008_user_preferences (additive), @ekklesia/core/account store, and the authenticated-baseline permission core.account.self.manage. - API: GET /api/v1/account (name, email, preferences) and partial PATCH /api/v1/account/preferences, self-only by construction: no user id is accepted anywhere; trusted Origin and body limit on the mutation. - Shell: the account control shows name, email, and initials (never the unvetted image URL) and offers Account, Appearance, Sign out; native radio groups apply a choice at once through the ADR 0060 controller and save it; the server's answer wins and a failed save reverts. - First paint: a classic, same-origin, render-blocking appearance.js in <head> applies a two-word localStorage cache before paint (no inline script, CSP unchanged). The account replaces the cache on every load, sign-out clears it, and other tabs follow via the storage event. - Deploy smoke: real-Chromium checks for light/dark, OS emulation, overrides, reload persistence, pre-paint, Shadow DOM modules, reduced motion, the display staying light, and phone width; the browser container gets a 512 MiB /dev/shm so a long session no longer fails requests with ERR_INSUFFICIENT_RESOURCES. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8571e6854c
commit
cd0287537e
@@ -209,9 +209,12 @@ export const SHELL_STYLESHEET = `
|
||||
gap: ${designToken("spacing.2xs")};
|
||||
left: 0;
|
||||
margin: 0;
|
||||
max-width: min(20rem, calc(100vw - 2 * ${designToken("spacing.md")}));
|
||||
min-width: 12rem;
|
||||
padding: ${designToken("spacing.xs")};
|
||||
position: absolute;
|
||||
/* As wide as a name and email need, within the cap, rather than the width of the sidebar. */
|
||||
width: max-content;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@@ -227,6 +230,126 @@ export const SHELL_STYLESHEET = `
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ek-shell-menu__items {
|
||||
display: grid;
|
||||
gap: ${designToken("spacing.2xs")};
|
||||
}
|
||||
|
||||
.ek-shell-menu__identity {
|
||||
border-bottom: ${designToken("border.width")} solid ${designToken("border.divider")};
|
||||
display: grid;
|
||||
gap: ${designToken("spacing.2xs")};
|
||||
min-width: 0;
|
||||
padding: ${designToken("spacing.xs")} ${designToken("spacing.sm")} ${designToken("spacing.sm")};
|
||||
}
|
||||
|
||||
.ek-shell-menu__identity-name {
|
||||
color: ${designToken("text.primary")};
|
||||
font-weight: ${designToken("typography.weight-medium")};
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ek-shell-menu__identity-email {
|
||||
color: ${designToken("text.secondary")};
|
||||
font-size: ${designToken("typography.size-sm")};
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ek-shell-account-trigger__copy {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.ek-shell-choice-group {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ek-shell-choice-group__legend {
|
||||
color: ${designToken("text.primary")};
|
||||
font-size: ${designToken("typography.size-heading-3")};
|
||||
font-weight: ${designToken("typography.weight-bold")};
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ek-shell-choice-group__description {
|
||||
color: ${designToken("text.secondary")};
|
||||
font-size: ${designToken("typography.size-sm")};
|
||||
margin: ${designToken("spacing.2xs")} 0 ${designToken("spacing.md")};
|
||||
}
|
||||
|
||||
.ek-shell-choice-group__options {
|
||||
display: grid;
|
||||
gap: ${designToken("spacing.xs")};
|
||||
}
|
||||
|
||||
/* One option of a radio group: the whole row is the label, so the whole row is the touch target.
|
||||
The chosen option is marked by the radio itself and by weight as well as by its background. */
|
||||
.ek-shell-choice {
|
||||
align-items: flex-start;
|
||||
border: ${designToken("border.width")} solid ${designToken("border.control")};
|
||||
border-radius: ${designToken("radius.md")};
|
||||
box-sizing: border-box;
|
||||
color: ${designToken("text.primary")};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: ${designToken("spacing.md")};
|
||||
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")};
|
||||
}
|
||||
|
||||
.ek-shell-choice:hover {
|
||||
background: ${designToken("state.hover-background")};
|
||||
}
|
||||
|
||||
.ek-shell-choice:has(.ek-shell-choice__input:focus-visible) {
|
||||
outline: ${designToken("state.focus-visible-width")} solid ${designToken("accent.focus")};
|
||||
outline-offset: ${designToken("state.focus-visible-offset")};
|
||||
}
|
||||
|
||||
.ek-shell-choice[data-checked="true"] {
|
||||
background: ${designToken("state.selected-background")};
|
||||
color: ${designToken("state.selected-foreground")};
|
||||
}
|
||||
|
||||
.ek-shell-choice__input {
|
||||
accent-color: ${designToken("accent.base")};
|
||||
flex: 0 0 auto;
|
||||
height: 1.25rem;
|
||||
margin: 0.125rem 0 0;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.ek-shell-choice__copy {
|
||||
display: grid;
|
||||
gap: ${designToken("spacing.2xs")};
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ek-shell-choice__label {
|
||||
font-weight: ${designToken("typography.weight-medium")};
|
||||
}
|
||||
|
||||
.ek-shell-choice[data-checked="true"] .ek-shell-choice__label {
|
||||
font-weight: ${designToken("typography.weight-bold")};
|
||||
}
|
||||
|
||||
.ek-shell-choice__description {
|
||||
color: ${designToken("text.secondary")};
|
||||
font-size: ${designToken("typography.size-sm")};
|
||||
}
|
||||
|
||||
.ek-shell-choice[data-checked="true"] .ek-shell-choice__description {
|
||||
color: ${designToken("state.selected-foreground")};
|
||||
}
|
||||
|
||||
.ek-shell-account-trigger__email {
|
||||
color: ${designToken("text.secondary")};
|
||||
font-size: ${designToken("typography.size-xs")};
|
||||
}
|
||||
|
||||
.ek-shell-mobile-bar {
|
||||
padding: ${designToken("spacing.sm")} ${designToken("spacing.md")} 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user