diff --git a/CHANGELOG.md b/CHANGELOG.md index c464dca..2021276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ tag (08 §3.1): `selfphoneng-api`, `selfphoneng-asterisk` and `selfphoneng-probe the Gitea registry. From v0.0.3 the release also has the probe as downloads for machines without Docker. +## v0.0.11 — 2026-10-28 + +**Updating:** no database changes. Portainer: change both tags to `0.0.11` and redeploy. Compose: +`sudo scripts/update.sh v0.0.11`. + +- **Phones:** on an iPhone a ringing call dimmed the screen but showed no Answer button (iOS + zoomed into the small sign-in fields and stayed zoomed, so the call dialog was off screen). + Fields are now 16px on touch screens and buttons at least 44px. A ringing call gets its own + screen: full screen on a phone, with big round Decline and Answer buttons. My Phone's tabs fold + into a menu button on narrow screens. +- **New look:** colors from the logo (blue, and its navy for dark mode), cards on an off-white + page, and text that is easy to read in both themes (all text at least WCAG AA). Icons + throughout. +- **Admin sidebar** grouped into Call routing, People, Features, History and System. +- **My Phone Home** is a set of cards: My status, Missed calls (with call back) and New + voicemails. +- **Browser phone:** a status pill (Ready, Connecting…), phone-style call buttons with icons + (Mute, Hold, Keypad, Transfer, Hang up) and a round keypad with the letters under the digits. + ## v0.0.10 — 2026-10-27 **Before you update:** this version changes the database (spam check); the api backs up first, diff --git a/README.md b/README.md index bf41d52..a337fb1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A fully open source, self-hosted business phone system. Bring your own SIP trunk and numbers (reference provider: voip.ms). Built on Asterisk 24 + TypeScript + PostgreSQL 18, runs in Docker. -**Status:** pre-1.0 (v0.0.10), for testing. See [what's left for v1](docs/v1-checklist.md). +**Status:** pre-1.0 (v0.0.11), for testing. See [what's left for v1](docs/v1-checklist.md). ## What it does today diff --git a/apps/api/package.json b/apps/api/package.json index ea84503..7974729 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "@selfphoneng/api", - "version": "0.0.10", + "version": "0.0.11", "private": true, "license": "AGPL-3.0-or-later", "type": "module", diff --git a/apps/probe/package.json b/apps/probe/package.json index ba3c34c..35fc284 100644 --- a/apps/probe/package.json +++ b/apps/probe/package.json @@ -1,6 +1,6 @@ { "name": "@selfphoneng/probe", - "version": "0.0.10", + "version": "0.0.11", "private": true, "license": "AGPL-3.0-or-later", "type": "module", diff --git a/apps/web/package.json b/apps/web/package.json index 6330667..aa1d0b6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@selfphoneng/web", - "version": "0.0.10", + "version": "0.0.11", "private": true, "license": "AGPL-3.0-or-later", "type": "module", @@ -13,6 +13,7 @@ "@hookform/resolvers": "^5.9.1", "@selfphoneng/shared": "workspace:*", "@tanstack/react-query": "^5.104.3", + "lucide-react": "^1.56.0", "react": "^19.3.1", "react-dom": "^19.3.1", "react-hook-form": "^7.90.0", diff --git a/apps/web/src/auth/RequireAuth.tsx b/apps/web/src/auth/RequireAuth.tsx index ad88152..21c1de3 100644 --- a/apps/web/src/auth/RequireAuth.tsx +++ b/apps/web/src/auth/RequireAuth.tsx @@ -26,7 +26,7 @@ export function RequireAuth({ }) { const me = useMe(); const loc = useLocation(); - if (me.isPending) return

Loading…

; + if (me.isPending) return

Loading…

; if (me.isError) { if (me.error instanceof ApiError && me.error.status === 401) return ; diff --git a/apps/web/src/components/DestinationPicker.tsx b/apps/web/src/components/DestinationPicker.tsx index 8a5f64f..d73d964 100644 --- a/apps/web/src/components/DestinationPicker.tsx +++ b/apps/web/src/components/DestinationPicker.tsx @@ -143,7 +143,7 @@ export function DestinationPicker({

)} {isIdKind(kind) && all[kind] && targets(kind).length === 0 && ( -

Nothing to pick yet ({what}).

+

Nothing to pick yet ({what}).

)} ); diff --git a/apps/web/src/components/Dialog.tsx b/apps/web/src/components/Dialog.tsx index bec6fa3..e056a97 100644 --- a/apps/web/src/components/Dialog.tsx +++ b/apps/web/src/components/Dialog.tsx @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // Native (focus trap + Esc for free) as a centered modal or a right-hand drawer. +import { X } from 'lucide-react'; import { useEffect, useRef, type ReactNode } from 'react'; import { Button } from './ui.tsx'; @@ -28,27 +29,27 @@ export function Dialog({ const shape = drawer ? 'ml-auto mr-0 h-dvh max-h-dvh w-full max-w-md' - : 'm-auto w-[calc(100%-2rem)] max-w-md rounded-lg'; + : 'm-auto w-[calc(100%-2rem)] max-w-md rounded-xl'; return ( e.target === ref.current && onClose()} aria-labelledby="dialog-title" - className={`${shape} bg-white p-0 text-zinc-900 shadow-xl backdrop:bg-black/40 dark:bg-zinc-900 dark:text-zinc-100`} + className={`${shape} bg-surface p-0 text-fg shadow-xl backdrop:bg-black/40`} >
-
+

{title}

{children}
{footer && ( -
+
{footer}
)} diff --git a/apps/web/src/components/ParkedCalls.tsx b/apps/web/src/components/ParkedCalls.tsx index ce099a1..585f247 100644 --- a/apps/web/src/components/ParkedCalls.tsx +++ b/apps/web/src/components/ParkedCalls.tsx @@ -15,13 +15,13 @@ export function ParkedCalls({ onPickUp }: { onPickUp?: (slot: string) => void }) Parked calls {parked.length === 0 ? ( -

+

None. Transfer a call to 700 to park it; dial the slot to pick it up.

) : (
    {parked.map((p) => (
  • void }) {p.slot} {p.callerName ? `${p.callerName} ` : ''} - {fmtNumber(p.callerNumber)} - {p.parkedBy && · parked by {p.parkedBy}} + {fmtNumber(p.callerNumber)} + {p.parkedBy && · parked by {p.parkedBy}} {since(p.since, now)} {onPickUp && (
)} {qr.error && {qr.error.message}} -

+

On a mobile phone, apps can miss calls while they're in the background (iPhones and battery-saving Android phones stop them). For calls you can't miss, use a desk phone or this browser, or forward unanswered calls to your mobile number. diff --git a/apps/web/src/components/SignInCard.tsx b/apps/web/src/components/SignInCard.tsx index 398e392..ec29db4 100644 --- a/apps/web/src/components/SignInCard.tsx +++ b/apps/web/src/components/SignInCard.tsx @@ -56,11 +56,11 @@ export function SignInCard() {

Password

{ways.password ? ( - + Change password ) : ( -

+

You have no password here. An admin can give you one with Reset password.

)} @@ -92,7 +92,7 @@ export function SignInCard() { ) : ( <> -

+

Connect your {oidc.label} account to sign in with it as well.

@@ -133,7 +133,7 @@ function DirectoryConnect() { }} >

Company directory

-

+

Connect your directory account to sign in with its password as well.

diff --git a/apps/web/src/components/SoundPicker.tsx b/apps/web/src/components/SoundPicker.tsx index a02b0fc..57cca16 100644 --- a/apps/web/src/components/SoundPicker.tsx +++ b/apps/web/src/components/SoundPicker.tsx @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // SoundPicker (05 §3): pick a recorded prompt, play it, or upload a new one right here. +import { Play } from 'lucide-react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useId, useRef } from 'react'; import { soundsApi } from '../api/client.ts'; @@ -63,7 +64,7 @@ export function SoundPicker({ void a.play().catch(() => undefined); }} > - ▶ + ); } diff --git a/apps/web/src/components/ui.tsx b/apps/web/src/components/ui.tsx index 7cbbb01..3a97d9c 100644 --- a/apps/web/src/components/ui.tsx +++ b/apps/web/src/components/ui.tsx @@ -3,15 +3,14 @@ import { useId, type ComponentProps, type ReactNode } from 'react'; import { APP_NAME } from '@selfphoneng/shared'; -const focus = - 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sky-500'; +export const focusRing = + 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus'; const variants = { - primary: 'bg-sky-600 text-white hover:bg-sky-700 disabled:bg-sky-600/50', - secondary: - 'border border-zinc-300 bg-white hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-900 dark:hover:bg-zinc-800', + primary: 'bg-primary text-white hover:bg-primary-hover disabled:bg-primary/50', + secondary: 'border border-line-strong bg-surface hover:bg-subtle', danger: 'bg-red-600 text-white hover:bg-red-700 disabled:bg-red-600/50', - ghost: 'hover:bg-zinc-100 dark:hover:bg-zinc-800', + ghost: 'hover:bg-subtle', }; /** The icon (public/icon-192.png, cut from public/logo.png) and the product name. */ @@ -32,13 +31,40 @@ export function Button({ return (
) : ( a && ( -

+

This is a development build: the source archive is only in builds made with Docker. The project's repository is below.

diff --git a/apps/web/src/pages/ChangePasswordPage.tsx b/apps/web/src/pages/ChangePasswordPage.tsx index e63a76d..106f36a 100644 --- a/apps/web/src/pages/ChangePasswordPage.tsx +++ b/apps/web/src/pages/ChangePasswordPage.tsx @@ -44,7 +44,7 @@ export function ChangePasswordPage() { return ( {forced && ( -

+

You signed in with a temporary password. Choose a new one to continue.

)} diff --git a/apps/web/src/pages/LoginPage.tsx b/apps/web/src/pages/LoginPage.tsx index 3358746..fb7b816 100644 --- a/apps/web/src/pages/LoginPage.tsx +++ b/apps/web/src/pages/LoginPage.tsx @@ -13,12 +13,12 @@ import { Alert, Button, Field, inputClass } from '../components/ui.tsx'; export function AuthCard({ title, children }: { title: string; children: React.ReactNode }) { return (
-
+
{APP_NAME}

{title}

{children}
- + About SelfPhoneNG · source code
@@ -73,12 +73,12 @@ export function LoginPage() { {/* A full page load: the browser goes to the provider and comes back signed in. */} Sign in with {oidc.label} {showForm && ( -

+

or with your password

)} @@ -87,7 +87,7 @@ export function LoginPage() { {!showForm && ( @@ -109,7 +110,7 @@ function Nav({ hint?: string; }) { return ( -
+
@@ -118,7 +119,7 @@ function Nav({ {skip} )} - {hint && {hint}} + {hint && {hint}}
); } @@ -127,7 +128,10 @@ function PasswordStep({ next }: { next: () => void }) { // Signing in with the initial password already forced a change (RequireAuth). return ( <> -

✓ The initial admin password has been changed.

+

+ + The initial admin password has been changed. +