Skip to content

YUI Logging Convention ​

Applies to all TypeScript in src/ and all Rust in src-tauri/src/.

Principles ​

  • Every log line goes through the project logger. TypeScript: createLogger(namespace) from src/logger.ts. Rust: log crate macros (log::info!, etc.). Raw console.*, println!, and eprintln! are forbidden.
  • The message is a stable event name: snake_case, no spaces, present-tense identifier (e.g. backend_call, vrm_loaded, state_change). pnpm logger:check (CI) refuses any other shape.
  • Context is a single structured object of named fields — not interpolated prose. Errors are carried as error: String(err) (TypeScript).
  • Messages and field values use English. Free-text sentences do not appear in the message position; fields carry the detail.
  • Failure variants use a dotted sub-event name (e.g. tier1.render_error, backend_call.unexpected_error).

Namespaces ​

kebab-case, flat, one logger per module. The namespace matches the module's file name.

FileNamespace
motion-controller.ts"motion-controller"
tts-pipeline.ts"tts-pipeline"
dispatcher/backend/backend-caller.ts"backend-caller"

Levels ​

LevelWhen to useExamples
errorAn operation failed in a way that is user-visible or affects state/datastart_motion, backend_call.unexpected_error
warnRecovered or degraded: a fallback was taken, malformed input dropped, an optional path skippedmouth_expression_missing, rpc_threw (one per broker outage)
infoState transitions and lifecycle milestonesvrm_loaded, state_change, backend_call, fire, turn
debugHigh-frequency or per-item detailper-segment synth, per-frame work, poll ticks

Turn gate events ​

dispatcher/core/turn-gate.ts logs at info.

EventFieldsWhen
turn_holdnoneAnother window holds the turn lock, so this window waits.
turn_grantedwaited_msThe waiting window received the lock after that many milliseconds.

Drop events ​

dispatcher/dispatcher.ts (namespace dispatcher) logs a drop line for every trigger it holds back or supersedes before its backend call, and for every backend call that failed. Fields: event_name, reason, seq_id. The level comes from the reason:

reasonLevelWhen
guardrail_dropinfoThe debounce/cooldown/rate-limit gate refused the fire.
not_configuredwarnThe backend call failed: no endpoint is configured.
parse_errorwarnThe backend call failed: the reply could not be parsed.
network_dropwarnThe backend call failed on the network.
network_stallwarnThe backend call stalled past its deadline.
http_4xx_droperrorThe backend answered the call with an HTTP 4xx.
superseded_by_userinfoThe user typed or spoke, so the turn was aborted or dropped.
stale_pendinginfoA second deferred turn arrived, so the older one is dropped.
degraded_dropwarnA non-user turn reached the gate during the degraded state.
global_gapinfoThe global proactive gap held the fire back.
addressed_capinfoThe third addressed turn in a row without a user turn; the cap is rate_limit.addressed_chain_max.

recentDrops(n) returns the last n records.

dispatcher/dispatcher.ts (namespace dispatcher) also logs turn_hook_failed at warn, with the field error, when an onTurnClosed subscriber throws: the turn summary was already logged and the pump goes on.

Relation events ​

io/chat/tools/relation.ts (namespace relation), io/bridge/inbox/relations-rpc.ts (namespace relations-rpc), and the Character tab's Relations section (namespace quick-ui in the pet window, settings-ui in the settings window) log at info.

EventFieldsWhen
relation_ratedtarget, delta, band, clampedA rate_relation call reached the store. target is user or a preset id, delta the change applied after the daily cap and the range, band the band after it.
relations_replacedtargetsAn ingress PUT /relations replaced the window's store; targets lists its keys.
relation_settarget, value, bandThe user released a Relations slider at a value other than the stored one. value is the slider's value, band the band the store holds after the set.
relations_toggleenabledThe user flipped the Relations switch.

Addressed events ​

io/chat/tools/address-character.ts (namespace address-character), dispatcher/sources/addressed/addressed-producer.ts (namespace addressed-producer), and dispatcher/sources/addressed/addressed-source.ts (namespace addressed-source) log at info.

NamespaceEventFieldsWhen
address-charactercharacter_addressedtoAn address_character call recorded the reply's recipient. to is the named preset id.
addressed-producerfiretoThe producer emitted the character-addressed event for a closed, spoken reply.
addressed-producerno_texttoThe closed addressed turn left no transcript entry to carry.
addressed-sourcefirefromThe addressed source pushed an addressed.character candidate for this preset.
addressed-sourcepush_rejectedfromThe bus refused the addressed candidate.

Routed user turns ​

dispatcher/sources/routed/routed-user-source.ts (namespace routed-user-source) logs at info. The done signal logs nothing.

NamespaceEventFieldsWhen
routed-user-sourcefirementionThe routed source submitted a user turn for a message the unified chat window sent to this preset. mention is character, everyone or none.

Unified chat window ​

ui/unified/unified-view.ts (namespace unified-view), ui/unified/sequence.ts (namespace sequence), io/window/openers/unified-window.ts (namespace unified-window), and the Talk tab's switch row (namespace quick-ui in the pet window, settings-ui in the settings window) log at info unless a row says otherwise.

NamespaceEventFieldsWhen
unified-viewroutetargets, mentionThe window sent a message. targets is how many presets it goes to, mention is character, everyone or none.
sequencesequence_timeoutpresetA preset gave no done signal for the current send in 60 seconds, so the sequence moved on.
unified-windowunified_window_create_errorerrorThe window could not be created (error level).
unified-windowunified_window_open_failederrorShowing or creating the window threw (warn level).
unified-windowunified_window_close_failederrorClosing the window threw (warn level).
quick-ui, settings-uiunified_chat_toggleenabledThe user flipped the Unified chat switch.

TypeScript Usage ​

ts
import { createLogger } from "../logger";
const log = createLogger("motion-controller");

log.info("state_change", { from, to });
log.error("start_motion", { id, error: String(err) });
log.warn("mouth_expression_missing", { key });

The second argument is a plain object of named fields. Do not interpolate values into the message string.

Rust Usage ​

The log crate is format-string based (no kv feature enabled). The message leads with the snake_case event name; context follows as field=value pairs in the format string.

rust
log::warn!("start_dragging_failed error={e}");
log::error!("screen_capture_failed monitor={index} error={e}");

Level semantics are identical to TypeScript.

console.* Gate ​

Raw console.* is forbidden in src/. The rule is enforced by a Biome noConsole lint rule, with exemptions for src/logger.ts, scripts/**, and src/**/*.live.test.ts. The lint CI job (pnpm lint) fails any other violation.