Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions e2e/admin-home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,17 +243,17 @@ test.describe('mobile', () => {
await advancedToggle(page).click()
await page.getByRole('button', { name: 'Device', exact: true }).click()

// Network and Security are collapsible, closed by default; open both.
// Network and After a power cut are collapsible, closed by default; open both.
await page.getByText('Network', { exact: true }).click()
await expect(page.getByLabel('WiFi SSID')).toBeVisible()
await expect(page.getByLabel('WiFi password')).toBeVisible()
await expect(page.getByRole('button', { name: 'Save to device', exact: true })).toBeVisible()

await page.getByText('Security', { exact: true }).click()
await expect(page.getByRole('heading', { name: 'Boot PIN', exact: true })).toBeVisible()
const unattendedWarning = page.getByText(/A boot PIN is typed over the USB cable/)
await page.getByText('After a power cut', { exact: true }).click()
await page.locator('summary', { hasText: 'Boot PIN' }).click()
const unattendedWarning = page.getByText(/a signer with a boot PIN stays locked/)
await expect(unattendedWarning).toBeVisible()
await expect(unattendedWarning).toContainText('signing and remote management cannot resume')
await expect(unattendedWarning).toContainText('Signing and remote management stop')
await expect(unattendedWarning).toContainText('use a vault key with phone unlock instead')

const overflow = await page.evaluate(
Expand Down
1 change: 1 addition & 0 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ body {
}
.field-input::placeholder { color: #555; }
.field-input:focus { outline: none; border-color: var(--green-dim); }
.field-input:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.field-input:disabled { opacity: 0.4; }
select.field-input { cursor: pointer; }
.field-hint { font-size: 0.8rem; color: var(--text-dim); line-height: 1.5; }
Expand Down
1,201 changes: 649 additions & 552 deletions src/components/DevicePanel.svelte

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion src/components/DevicePanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ vi.mock('../lib/device.svelte.js', async () => {
const state: Record<string, unknown> = {
connected: true, mode: 'relay', error: null, masters: [], slots: [],
relayStatus: null, bridgeAuthed: false, connectionGeneration: 0,
vaultRelayTarget: null, vaultUnlockRequest: null, vaultReconnect: null,
}
const device = new Proxy(state, {
get(target, property, receiver) {
Expand All @@ -45,6 +46,9 @@ vi.mock('../lib/device.svelte.js', async () => {
usbDisplayFlip: vi.fn().mockResolvedValue(null),
setDisplayFlip: vi.fn(),
getFirmwareVersion: vi.fn().mockResolvedValue(null),
refreshMasters: vi.fn().mockResolvedValue(undefined),
sendVaultKeyOverRelay: vi.fn().mockResolvedValue(undefined),
vaultRelayDevicePub: vi.fn(() => ''),
listUnlockPhones: vi.fn(),
revokeUnlockPhone: vi.fn(),
setAnnounceOperator: vi.fn(),
Expand Down Expand Up @@ -111,6 +115,7 @@ describe('DevicePanel: the session override on a fresh firmware report', () => {
// Without the fix, the session's own "PIN set" snapshot would win forever
// and this would stay "Waits for Sapwood" even once FIRMWARE_INFO
// disagreed; the retired override lets the fresh report show through.
await waitFor(() => expect(screen.getByText('Not encrypted: anyone holding it can read the keys')).toBeTruthy())
await waitFor(() => expect(screen.getAllByText('Not encrypted').length).toBeGreaterThan(0))
expect(screen.getByText('Anyone holding it can read the keys.')).toBeTruthy()
})
})
3 changes: 2 additions & 1 deletion src/components/PhoneHandoff.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,9 @@
.handoff { margin-top: 1.5rem; }
.handoff .section-title { font-size: 0.95rem; color: var(--green); }
.field { margin-bottom: 0.9rem; }
.qr { width: 184px; padding: 12px; background: #fff; border-radius: 6px; margin-bottom: 0.9rem; }
.qr { width: 220px; padding: 12px; background: #fff; border-radius: 6px; margin-bottom: 0.9rem; }
.qr :global(svg) { display: block; width: 100%; height: auto; }
@media (max-height: 500px) { .qr { width: 180px; } }
.handoff-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.plain-link {
display: block; margin: 0.9rem 0 0; padding: 0; width: 100%;
Expand Down
50 changes: 50 additions & 0 deletions src/components/TogglePair.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
// Two mutually exclusive states shown as a pressed/unpressed button pair
// (Upright/Flipped, Detailed/Quiet, On/Off): the selected option stays
// enabled and full-opacity, with aria-pressed carrying state to assistive
// tech instead of colour or disablement alone.
interface Props {
label: string
onLabel: string
offLabel: string
/** true selects onLabel, false selects offLabel. */
value: boolean
pending?: boolean
/** Disables switching to "on" specifically (e.g. no phones to turn the
* announcement off for). */
onDisabled?: boolean
onchange: (value: boolean) => void
}
let { label, onLabel, offLabel, value, pending = false, onDisabled = false, onchange }: Props = $props()

function press(next: boolean) {
if (next === value) return // pressing the pressed button does nothing
onchange(next)
}
</script>

<div class="toggle-pair" role="group" aria-label={label}>
<button
type="button"
class="btn btn-sm"
class:btn-secondary={!value}
class:lq-on={value}
aria-pressed={value}
disabled={pending}
onclick={() => press(true)}
>{onLabel}</button>
<button
type="button"
class="btn btn-sm"
class:btn-secondary={value}
class:lq-on={!value}
aria-pressed={!value}
disabled={pending || onDisabled}
onclick={() => press(false)}
>{offLabel}</button>
</div>

<style>
.toggle-pair { display: flex; gap: 0.5rem; }
.lq-on { border-color: var(--green-dim); color: var(--green); background: #08130d; opacity: 1; }
</style>
Loading
Loading