Merge branch 'dev' into feature/funk

# Conflicts:
#	frontend/testserver/index.cjs
#	sky_phone/config/config.lua
#	sky_phone/source/html/index.html
This commit is contained in:
DerEchteAlec
2026-08-06 22:04:38 +02:00
44 changed files with 5498 additions and 284 deletions
+18
View File
@@ -0,0 +1,18 @@
import { describe, expect, it } from 'vitest'
import { parseDatabaseDate } from './date'
describe('database dates', () => {
it('parses SQL date strings', () => {
expect(parseDatabaseDate('2026-08-06 17:30:00').getTime()).toBe(
new Date('2026-08-06T17:30:00').getTime(),
)
})
it('parses Unix timestamps in seconds and milliseconds', () => {
expect(parseDatabaseDate(1_786_034_600).getTime()).toBe(1_786_034_600_000)
expect(parseDatabaseDate(1_786_034_600_000).getTime()).toBe(
1_786_034_600_000,
)
})
})
+10
View File
@@ -0,0 +1,10 @@
export type DatabaseDateValue = string | number
export function parseDatabaseDate(value: DatabaseDateValue): Date {
if (typeof value === 'number') {
const timestamp = Math.abs(value) < 1_000_000_000_000 ? value * 1000 : value
return new Date(timestamp)
}
return new Date(value.replace(' ', 'T'))
}
+13 -1
View File
@@ -1,9 +1,21 @@
import { describe, expect, it } from 'vitest'
import { clampPage } from './pages'
import { clampPage, paginateItems } from './pages'
describe('page clamping', () => {
it('keeps pages in range', () => {
expect(clampPage(-4)).toBe(0)
expect(clampPage(1)).toBe(1)
expect(clampPage(9)).toBe(2)
})
it('splits overflowing app grids into additional pages', () => {
expect(
paginateItems(
Array.from({ length: 21 }, (_, index) => index),
20,
),
).toEqual([
Array.from({ length: 20 }, (_, index) => index),
[20],
])
})
})
+9
View File
@@ -1,5 +1,14 @@
export const SPRINGBOARD_PAGE_COUNT = 3
export function paginateItems<T>(items: readonly T[], pageSize: number): T[][] {
if (!Number.isInteger(pageSize) || pageSize <= 0) return []
const pages: T[][] = []
for (let index = 0; index < items.length; index += pageSize) {
pages.push(items.slice(index, index + pageSize))
}
return pages
}
export function clampPage(
page: number,
pageCount = SPRINGBOARD_PAGE_COUNT,
+1
View File
@@ -11,5 +11,6 @@ describe('phone numbers', () => {
it('formats keypad input incrementally', () => {
expect(formatPhoneNumber('5551234567')).toBe('555 123 4567')
expect(formatPhoneNumber('5551')).toBe('555 1')
expect(formatPhoneNumber(5551234567)).toBe('555 123 4567')
})
})
+2 -2
View File
@@ -5,8 +5,8 @@ export function normalizePhoneNumber(value: string): string | null {
return digits.length === PHONE_NUMBER_LENGTH ? digits : null
}
export function formatPhoneNumber(value: string): string {
const digits = value.replace(/\D/g, '').slice(0, PHONE_NUMBER_LENGTH)
export function formatPhoneNumber(value: string | number): string {
const digits = String(value).replace(/\D/g, '').slice(0, PHONE_NUMBER_LENGTH)
const groups = [digits.slice(0, 3), digits.slice(3, 6), digits.slice(6, 10)]
return groups.filter(Boolean).join(' ')
}
+5
View File
@@ -16,6 +16,7 @@ describe('preferences', () => {
notificationVolume: 45,
notificationDurationSeconds: 14,
notifications: {
messages: { enabled: false, sounds: false },
clock: { enabled: false, sounds: false },
},
phoneScale: 110,
@@ -26,6 +27,10 @@ describe('preferences', () => {
expect(value.settings.appearanceMode).toBe('light')
expect(value.settings.notificationVolume).toBe(45)
expect(value.settings.notificationDurationSeconds).toBe(14)
expect(value.settings.notifications.messages).toEqual({
enabled: false,
sounds: false,
})
expect(value.settings.notifications.clock).toEqual({
enabled: false,
sounds: false,
+1
View File
@@ -49,6 +49,7 @@ const DEFAULT_APP_NOTIFICATIONS: Record<
AppNotificationPreferences
> = {
phone: { enabled: true, sounds: true },
messages: { enabled: true, sounds: true },
'app-store': { enabled: true, sounds: true },
calculator: { enabled: true, sounds: true },
snake: { enabled: true, sounds: true },