Merge pull request 'Finish the console object-argument sweep' (#108) from fix/log-object-args-sweep into dev

Reviewed-on: #108
This commit is contained in:
padreug 2026-09-24 13:09:55 +00:00
commit 48c71f7902
4 changed files with 43 additions and 17 deletions

View file

@ -5,3 +5,26 @@ import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/**
* Render a denomination/count list for the journal.
*
* Electron's console bridge stringifies every console argument on its way to
* the journal, so passing the array itself arrives as `[object Object]` and
* the numbers are lost. Interpolate one of these instead. See CLAUDE.md,
* "Useful invariants when debugging".
*/
export function formatBays(rows: { denomination: number; count?: number }[]): string {
if (!rows?.length) return '(none)'
// `count` is optional on a device-config cassette: a preset can declare the
// denomination a bay holds without claiming how many notes are in it. Show
// that as unknown rather than as zero, which would read as a drained bay.
return rows.map((r) => `${r.denomination}x${r.count ?? '?'}`).join(' ')
}
/** Same, for a denomination-keyed count map as `getInventory()` returns. */
export function formatInventory(inv: Record<number, number>): string {
const entries = Object.entries(inv ?? {})
if (!entries.length) return '(none)'
return entries.map(([denom, count]) => `${denom}x${count}`).join(' ')
}

View file

@ -15,6 +15,7 @@
*/
import type { ATMServices } from '@bitSpire/state-machine'
import { formatBays } from '@/lib/utils'
export interface CassetteConfig {
denomination: number
@ -126,7 +127,7 @@ export async function initializeHalServices(config: HalConfig): Promise<HalServi
const atmServices: Pick<ATMServices, 'dispenseCash' | 'getInventory'> = {
dispenseCash: async (amounts) => {
console.log('[HAL] Dispensing:', amounts)
console.log(`[HAL] Dispensing: ${formatBays(amounts)}`)
// Re-initialize dispenser if it was closed after a previous error
if (!dispenser.initialized) {

View file

@ -21,6 +21,7 @@ import type { ATMServices, ATMContext } from '@bitSpire/state-machine'
// Import Electron types
import type {} from '@/types/electron'
import { formatBays, formatInventory } from '@/lib/utils'
// Check if we're running in Electron (electronAPI is exposed via preload)
const isElectron = typeof window !== 'undefined' && window.electronAPI !== undefined
@ -1092,7 +1093,7 @@ export function createATMServices(
* Dispense cash (mock for development)
*/
dispenseCash: async (amounts) => {
console.log('[ATM Service] Dispensing cash:', amounts)
console.log(`[ATM Service] Dispensing cash: ${formatBays(amounts)}`)
// In production, this would interface with the Rust HAL
// For now, simulate dispense delay
@ -1269,7 +1270,7 @@ export function createATMServices(
20: 50, // 50 x $20 bills = $1000 capacity
}
console.log('[ATM Service] Inventory:', inventory)
console.log(`[ATM Service] Inventory: ${formatInventory(inventory)}`)
return inventory
},
}

View file

@ -15,6 +15,7 @@ import { initializeLightningServices, fetchBtcPrice } from '@/services/lightning
import { classifyInitError } from '@/services/init-error'
import { startOperatorConfigService, type OperatorConfigService } from '@/services/operator-config'
import { startOperatorFeesService, type OperatorFeesService } from '@/services/operator-fees'
import { formatBays, formatInventory } from '@/lib/utils'
import type { HalConfig, HalServices } from '@/services/hal'
import type { MachineModel } from '@/config'
import type { LightningBackend } from '@/services/lightning'
@ -179,7 +180,7 @@ async function loadInventoryFromDb(): Promise<Record<number, number> | null> {
if (isElectron && window.electronAPI) {
try {
const inv = await window.electronAPI.getInventory()
console.log('[ATM] Loaded inventory from DB:', inv)
console.log(`[ATM] Loaded inventory from DB: ${formatInventory(inv)}`)
return inv
} catch (e) {
console.warn('[ATM] Failed to load inventory from DB:', e)
@ -245,7 +246,7 @@ const mockServices: ATMServices = {
},
dispenseCash: async (amounts) => {
console.log('[Mock] Dispensing cash:', amounts)
console.log(`[Mock] Dispensing cash: ${formatBays(amounts)}`)
await new Promise((resolve) => setTimeout(resolve, 2000))
return {
bills: amounts.map((a) => ({
@ -474,7 +475,7 @@ export const useAtmStore = defineStore('atm', () => {
// the availability beacon kept advertising a full cassette.
if (inv === null) return
persistedInventory.value = inv
console.log('[ATM] Persisted inventory updated:', inv)
console.log(`[ATM] Persisted inventory updated: ${formatInventory(inv)}`)
}
/** Detect Bitcoin network from a BOLT-11 invoice prefix (called once, persisted) */
@ -1589,7 +1590,7 @@ export const useAtmStore = defineStore('atm', () => {
console.log('[ATM] Fiat currency:', devConfig.fiatCode)
console.log('[ATM] Validator device:', devConfig.validator.device)
console.log('[ATM] Dispenser device:', devConfig.dispenser.device)
console.log('[ATM] Cassettes:', devConfig.dispenser.cassettes)
console.log(`[ATM] Cassettes: ${formatBays(devConfig.dispenser.cassettes)}`)
const halConfig = toHalConfig(devConfig)
await initializeWithHalIpc(halConfig)
@ -1662,7 +1663,7 @@ export const useAtmStore = defineStore('atm', () => {
// HAL dispenseCash via IPC
const halAtmServices: Pick<ATMServices, 'dispenseCash' | 'getInventory'> = {
dispenseCash: async (amounts) => {
console.log('[ATM] Dispensing via IPC:', amounts)
console.log(`[ATM] Dispensing via IPC: ${formatBays(amounts)}`)
return await api.halDispense(amounts)
},
getInventory: async () => {
@ -1850,15 +1851,15 @@ export const useAtmStore = defineStore('atm', () => {
credentialIdHash: string
reason?: string
}) {
console.info('[Access] audit', {
result: outcome.result,
role: outcome.role ?? null,
// Truncate the hash in logs — it's already non-reversible, but no need to
// splash the full value across the journal.
credentialIdHash: outcome.credentialIdHash.slice(0, 12),
reason: outcome.reason ?? null,
at: Date.now(),
})
// Truncate the hash — it's already non-reversible, but no need to splash
// the full value across the journal. Interpolated rather than passed as an
// object: the console bridge would stringify it to `[object Object]` and
// this line is the audit trail until #90 persists it to state.db.
console.info(
`[Access] audit result=${outcome.result} role=${outcome.role ?? 'none'} ` +
`hash=${outcome.credentialIdHash.slice(0, 12)} ` +
`reason=${outcome.reason ?? 'none'} at=${Date.now()}`
)
}
/** Grant terminal access after a credential (and any PIN) is authorized. */