Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
f32e0e0
Scaffold Periscope modules: Core, UI, and Tools
kyleve Jul 8, 2026
65dc66b
Add Periscope event model: LogEvent, Message, LogLevel
kyleve Jul 8, 2026
f41e946
Add Log<Event>: scope tree, id scoping, links via +
kyleve Jul 8, 2026
1f8eda4
Add Periscope system: LogSink pipeline, recorder, OSLog sink
kyleve Jul 8, 2026
6d8c89e
Add pipeline policies: level floors, flush/drop, redaction
kyleve Jul 8, 2026
39c4d60
Add LogContextProviding: derived per-instance loggers in Core
kyleve Jul 8, 2026
b24a075
Add task-local context propagation: withContext and Log.current
kyleve Jul 8, 2026
1b8f530
Add PeriscopeStore: SwiftData persistence, sessions, queries
kyleve Jul 8, 2026
a17dc79
Add the tag system: tagged contexts, stamping, tag queries
kyleve Jul 8, 2026
d07e770
Add spans: measure, begin/end pairs, os_signpost mirroring
kyleve Jul 8, 2026
27eb055
Add attachments: LogAttachment API and external-storage persistence
kyleve Jul 8, 2026
76567fd
Add ambient event sources: protocol and built-in observers
kyleve Jul 8, 2026
3e80054
Document PeriscopeCore: full README and AGENTS invariants
kyleve Jul 8, 2026
bfa036a
Add PeriscopeUI: logContext modifier and environment accessor
kyleve Jul 8, 2026
ce9099e
Add PeriscopeTools latest-logs viewer with NDJSON export
kyleve Jul 8, 2026
e964ba8
Add PeriscopeTools log tracer across time and hierarchy
kyleve Jul 8, 2026
db75b15
Add PeriscopeTools hookable debug toast for warning+ events
kyleve Jul 8, 2026
bf3af92
Add PeriscopeTools log view mode for per-view event inspection
kyleve Jul 8, 2026
0361a2e
Roll back failed PeriscopeStore saves so one bad batch can't wedge
kyleve Jul 8, 2026
db7092b
Evict instance scopes on dealloc; key the registry by InstanceID
kyleve Jul 8, 2026
6a91d8c
Subscribe to store changes before the initial load in live models
kyleve Jul 8, 2026
eb096ac
Coalesce auto-flushes: one task per storm, not one per record
kyleve Jul 8, 2026
4ee7299
Bound liveRecords() observer buffers with bufferingNewest
kyleve Jul 8, 2026
7864429
Add span lifecycle: lifetimes, exits, expiry, relaunch policy
kyleve Jul 8, 2026
55c7937
Make drain chunking linear instead of quadratic
kyleve Jul 8, 2026
06b4001
Prefetch tags and attachments on store event reads
kyleve Jul 8, 2026
3e05f89
Add pipeline fuzz + drop-policy tests; fix sink-replay ordering
kyleve Jul 8, 2026
f6e0f48
Check level floors before running redaction
kyleve Jul 8, 2026
c3e42c0
Merge origin/main (Foreman removal) into cursor/periscope
kyleve Jul 8, 2026
b9f5a59
Close out the macOS-compilation TODO: package is iOS-only now
kyleve Jul 8, 2026
aebba99
Trim the tracer trail to events strictly before the origin
kyleve Jul 8, 2026
f5b8bb7
Add optional budgets to measure spans with mid-hang overdue warnings
kyleve Jul 8, 2026
535cf81
Cancel the prior network monitor on ambient source restart
kyleve Jul 8, 2026
b226eef
Rebind tool views when their inputs change in place
kyleve Jul 8, 2026
ff720fe
Sync the inspect-mode mirror both ways
kyleve Jul 8, 2026
3c2d279
Add OpenSpansView: the open-spans developer surface
kyleve Jul 8, 2026
cf1b94d
Make span exits first-class in the store and tooling
kyleve Jul 8, 2026
f235ba0
Add derive-and-emit overloads so trailing-closure chains compile
kyleve Jul 8, 2026
bf4c5d1
Cache notification authorization in the alert handler
kyleve Jul 8, 2026
3171432
Keep the orphan sweep's launch cost off the row width
kyleve Jul 8, 2026
068f916
Yield inspect-mode changes inside the lock
kyleve Jul 8, 2026
043f27d
Floor span pairs together: begin decides, the lifecycle follows
kyleve Jul 8, 2026
166cb7d
Hold the system weakly from the span watchdog
kyleve Jul 8, 2026
3c4928d
Build the events predicate as statements to fix the CI type-check
kyleve Jul 8, 2026
7f7d5ae
Serialize overdue sentinels with their span's end
kyleve Jul 8, 2026
fedc2c3
Consolidate scope-path walking into LogScope.ancestry
kyleve Jul 8, 2026
df39e09
Speed up window animations in showHosted like show()
kyleve Jul 8, 2026
b58a5d5
Reset viewer export state when the store is swapped
kyleve Jul 8, 2026
bdde06e
Fuzz span lifecycles, floor flips, and supersession
kyleve Jul 8, 2026
81b953d
Record a span's began atomically with its registration
kyleve Jul 8, 2026
32b300a
Exempt span pairs from the overflow drop policy
kyleve Jul 8, 2026
cd33fdb
Yield to live observers under the state lock, via one shared helper
kyleve Jul 8, 2026
93b2937
Make redaction transform-only for span pair records
kyleve Jul 8, 2026
189bca1
Organize Periscope Sources into per-concern subdirectories
kyleve Jul 8, 2026
7df2a43
Document Log()'s .shared default as a deliberate exception
kyleve Jul 9, 2026
70020d6
Assert instead of swallowing LogAttachment.error encode failures
kyleve Jul 9, 2026
830949d
Mark unparseable payloads in NDJSON exports instead of omitting them
kyleve Jul 9, 2026
1582b1e
Extract and test LogEventDetailView's payload decode helpers
kyleve Jul 9, 2026
04b9138
Document SpanName's String default honestly
kyleve Jul 9, 2026
7049753
Add Broadway TODO do the TODOs.md for Periscope
kyleve Jul 14, 2026
da2b544
Retain ambient observer tokens; add stopAmbientSources()
kyleve Jul 14, 2026
dcc0e46
Type LogAttachment.contentType as an enum with .other
kyleve Jul 14, 2026
e96bdc1
Let custom LogLevels carry their own OSLogType
kyleve Jul 14, 2026
89fec06
Round out SpanExit's reason-taking factories
kyleve Jul 14, 2026
0afe4b5
Tags: typed values, ordered [LogTag] lists, multi-tag queries
kyleve Jul 14, 2026
d460816
Store the dynamic type on InstanceID, deriving its name on demand
kyleve Jul 14, 2026
89fbfd7
Make drop protection a LogEvent opt-in, not a type check
kyleve Jul 14, 2026
800a8d8
Raise tool event caps to 500 and make them per-instance
kyleve Jul 14, 2026
72bee98
Persist a StoreWriteFailed marker after rolled-back writes
kyleve Jul 14, 2026
12e0e05
Prune orphaned sessions, tags, and scope branches with retention
kyleve Jul 14, 2026
dc80153
Capture #function/#fileID on log emits
kyleve Jul 14, 2026
2771113
Add LogEvent.externalID linking events to their objects
kyleve Jul 14, 2026
3f9bae3
Add an accessibility-settings ambient source
kyleve Jul 14, 2026
f19be1f
Record the PR review pass and the staged design loops in TODOs
kyleve Jul 14, 2026
45b4b2b
Merge main: TestHostSupport, Broadway, Where extensions
kyleve Jul 15, 2026
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
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| SwiftFormat | 0.60.1 | `.mise.toml` |
| Swift PM | 6.2 | `Package.swift` (`swift-tools-version`) |

**Libraries** (**StuffCore**, **LifecycleKit**, **LogKit**, **LogViewerUI**, **TestHostSupport**, **RegionKit**, **WhereCore**, **WhereUI**) are defined in the root [`Package.swift`](Package.swift) — local package for libraries, Tuist for apps and test bundles.
**Libraries** (**StuffCore**, **LifecycleKit**, **LogKit**, **LogViewerUI**, **PeriscopeCore**, **PeriscopeUI**, **PeriscopeTools**, **TestHostSupport**, **RegionKit**, **WhereCore**, **WhereUI**) are defined in the root [`Package.swift`](Package.swift) — local package for libraries, Tuist for apps and test bundles.

Tuist manifests live at the repo root ([`Project.swift`](Project.swift), [`Tuist.swift`](Tuist.swift)). `Project.swift` references `Package.local(path: .relativeToRoot("."))` and declares the **Where** app, **StuffTestHost**, and unit-test targets that depend on package products.

Expand Down Expand Up @@ -53,8 +53,8 @@ by `./sync-agents`.

## Targets

- **Package products** ([`Package.swift`](Package.swift)) — **StuffCore** ([`Shared/StuffCore/Sources/`](Shared/StuffCore/Sources/)), **LifecycleKit** ([`Shared/LifecycleKit/Sources/`](Shared/LifecycleKit/Sources/)), **LogKit** ([`Shared/LogKit/Sources/`](Shared/LogKit/Sources/), the logging facade), **LogViewerUI** ([`Shared/LogViewerUI/Sources/`](Shared/LogViewerUI/Sources/), the generic SwiftUI log viewer), **SwiftDataInspector** ([`Shared/SwiftDataInspector/Sources/`](Shared/SwiftDataInspector/Sources/), the generic SwiftData browser), and **TestHostSupport** ([`Shared/TestHostSupport/Sources/`](Shared/TestHostSupport/Sources/), the shared UIKit hosting + run-loop helpers for hosted test bundles) under [`Shared/`](Shared/); the **Broadway** design-system libraries **BroadwayCore** / **BroadwayUI** under [`Shared/Broadway/`](Shared/Broadway/) (see [`Shared/Broadway/AGENTS.md`](Shared/Broadway/AGENTS.md)); **RegionKit** ([`Where/RegionKit/Sources/`](Where/RegionKit/Sources/), the geometry + GeoJSON + region-lookup engine WhereCore builds on) / **WhereCore** / **WhereUI** under [`Where/`](Where/).
- **Tuist targets** ([`Project.swift`](Project.swift)) — **Where** app ([`Where/Where/`](Where/Where/)) and its embedded app extensions **WhereWidgets** ([`Where/WhereWidgets/`](Where/WhereWidgets/), home/lock-screen widgets) and **WhereShareExtension** ([`Where/WhereShareExtension/`](Where/WhereShareExtension/), a Share-sheet action that saves shared content as evidence into the App Group store), **RegionViewer** ([`Where/RegionViewer/`](Where/RegionViewer/), a thin standalone **Mac Catalyst** host for the WhereUI region-map developer tool — the only target with a `.macCatalyst` destination), **BroadwayCatalog** ([`Shared/Broadway/BroadwayCatalog/`](Shared/Broadway/BroadwayCatalog/), the Broadway component showcase app), **StuffTestHost** ([`Shared/StuffTestHost/`](Shared/StuffTestHost/)), **WhereTests** (app tests, no host), and hosted **\*Tests** bundles (**StuffCoreTests**, **LifecycleKitTests**, **LogKitTests**, **LogViewerUITests**, **SwiftDataInspectorTests**, **RegionKitTests**, **WhereCoreTests**, **WhereUITests**, **BroadwayCoreTests**, **BroadwayUITests**, **BroadwayCatalogTests**) that depend on **StuffTestHost** + **TestHostSupport** + the relevant package product (most via the shared `unitTests` helper).
- **Package products** ([`Package.swift`](Package.swift)) — **StuffCore** ([`Shared/StuffCore/Sources/`](Shared/StuffCore/Sources/)), **LifecycleKit** ([`Shared/LifecycleKit/Sources/`](Shared/LifecycleKit/Sources/)), **LogKit** ([`Shared/LogKit/Sources/`](Shared/LogKit/Sources/), the logging facade), **LogViewerUI** ([`Shared/LogViewerUI/Sources/`](Shared/LogViewerUI/Sources/), the generic SwiftUI log viewer), **PeriscopeCore** / **PeriscopeUI** / **PeriscopeTools** ([`Shared/Periscope/`](Shared/Periscope/), the typed hierarchical observability framework — core + SwiftData store, SwiftUI environment integration, and on-device exploration tooling), **SwiftDataInspector** ([`Shared/SwiftDataInspector/Sources/`](Shared/SwiftDataInspector/Sources/), the generic SwiftData browser), and **TestHostSupport** ([`Shared/TestHostSupport/Sources/`](Shared/TestHostSupport/Sources/), the shared UIKit hosting + run-loop helpers for hosted test bundles) under [`Shared/`](Shared/); the **Broadway** design-system libraries **BroadwayCore** / **BroadwayUI** under [`Shared/Broadway/`](Shared/Broadway/) (see [`Shared/Broadway/AGENTS.md`](Shared/Broadway/AGENTS.md)); **RegionKit** ([`Where/RegionKit/Sources/`](Where/RegionKit/Sources/), the geometry + GeoJSON + region-lookup engine WhereCore builds on) / **WhereCore** / **WhereUI** under [`Where/`](Where/).
- **Tuist targets** ([`Project.swift`](Project.swift)) — **Where** app ([`Where/Where/`](Where/Where/)) and its embedded app extensions **WhereWidgets** ([`Where/WhereWidgets/`](Where/WhereWidgets/), home/lock-screen widgets) and **WhereShareExtension** ([`Where/WhereShareExtension/`](Where/WhereShareExtension/), a Share-sheet action that saves shared content as evidence into the App Group store), **RegionViewer** ([`Where/RegionViewer/`](Where/RegionViewer/), a thin standalone **Mac Catalyst** host for the WhereUI region-map developer tool — the only target with a `.macCatalyst` destination), **BroadwayCatalog** ([`Shared/Broadway/BroadwayCatalog/`](Shared/Broadway/BroadwayCatalog/), the Broadway component showcase app), **StuffTestHost** ([`Shared/StuffTestHost/`](Shared/StuffTestHost/)), **WhereTests** (app tests, no host), and hosted **\*Tests** bundles (**StuffCoreTests**, **LifecycleKitTests**, **LogKitTests**, **LogViewerUITests**, **PeriscopeCoreTests**, **PeriscopeUITests**, **PeriscopeToolsTests**, **SwiftDataInspectorTests**, **RegionKitTests**, **WhereCoreTests**, **WhereUITests**, **BroadwayCoreTests**, **BroadwayUITests**, **BroadwayCatalogTests**) that depend on **StuffTestHost** + **TestHostSupport** + the relevant package product (most via the shared `unitTests` helper).
- Add SPM library targets in `Package.swift` and wire apps/tests in `Project.swift` (see existing `unitTests` helper). A new module also ships a root `README.md` and `AGENTS.md` — see [Per-module docs](#per-module-docs).
- **CI scheme**: CI runs the explicit shared **Stuff-iOS-Tests** scheme (all test bundles) rather than the autogenerated `Stuff-Workspace` scheme. New test bundles must be added to the `Stuff-iOS-Tests` scheme in `Project.swift` or CI won't run them.
- **Never double-link a package product into a test bundle that already gets it through a dynamic-framework dependency.** Xcode's default SPM integration (how `Package.local` is wired here) embeds a product's code into *every* image that links it. **WhereUI** is a dynamic framework that statically embeds its own dependencies (WhereCore, BroadwayCore/BroadwayUI, LifecycleKit, LogViewerUI, SwiftDataInspector, …), so a `*Tests` bundle that depends on **WhereUI** *and* re-lists one of those in `extraPackageProducts` ends up with a **second copy** of it. When the shared **StuffTestHost** loads several `.xctest` bundles into one process, that leaves duplicate **type metadata** for the module, and any *type-keyed runtime lookup that crosses the WhereUI boundary* — SwiftUI `EnvironmentKey`s, `UITraitBridgedEnvironmentKey` bridging, the type-keyed `BTraits`/`BThemes`/`BStylesheets` containers — silently resolves against the wrong copy and returns the default (the writer stores under one copy's key *type*, the reader looks it up under another's). It only reproduces in the **full multi-bundle scheme** (not isolated `tuist test WhereUITests` runs) and is papered over by newer Xcode linkers, so it is brutal to diagnose (it cost ~2 hours once). **Depend on such products only transitively via `WhereUI`; keep them out of `extraPackageProducts`.** `WhereStylesheetTests.resolvesTraitAwareTokensFromTheBroadwayRoot` is the regression guard — it exercises a `\.stylesheet` (→ `\.bContext`) read across the boundary and fails if a duplicate copy returns.
Expand Down
22 changes: 22 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ let package = Package(
.library(name: "LifecycleKit", targets: ["LifecycleKit"]),
.library(name: "LogKit", targets: ["LogKit"]),
.library(name: "LogViewerUI", targets: ["LogViewerUI"]),
.library(name: "PeriscopeCore", targets: ["PeriscopeCore"]),
.library(name: "PeriscopeUI", targets: ["PeriscopeUI"]),
.library(name: "PeriscopeTools", targets: ["PeriscopeTools"]),
.library(name: "SwiftDataInspector", targets: ["SwiftDataInspector"]),
.library(name: "TestHostSupport", targets: ["TestHostSupport"]),
.library(name: "RegionKit", targets: ["RegionKit"]),
Expand Down Expand Up @@ -47,6 +50,25 @@ let package = Package(
],
path: "Shared/LogViewerUI/Sources",
),
.target(
name: "PeriscopeCore",
path: "Shared/Periscope/PeriscopeCore/Sources",
),
.target(
name: "PeriscopeUI",
dependencies: [
.target(name: "PeriscopeCore"),
],
path: "Shared/Periscope/PeriscopeUI/Sources",
),
.target(
name: "PeriscopeTools",
dependencies: [
.target(name: "PeriscopeCore"),
.target(name: "PeriscopeUI"),
],
path: "Shared/Periscope/PeriscopeTools/Sources",
),
.target(
name: "SwiftDataInspector",
path: "Shared/SwiftDataInspector/Sources",
Expand Down
32 changes: 32 additions & 0 deletions Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,29 @@ let project = Project(
productDependency: "LogViewerUI",
sources: ["Shared/LogViewerUI/Tests/**"],
),
unitTests(
name: "PeriscopeCoreTests",
bundleIdSuffix: "periscopecore",
productDependency: "PeriscopeCore",
sources: ["Shared/Periscope/PeriscopeCore/Tests/**"],
),
unitTests(
name: "PeriscopeUITests",
bundleIdSuffix: "periscopeui",
productDependency: "PeriscopeUI",
sources: ["Shared/Periscope/PeriscopeUI/Tests/**"],
extraPackageProducts: ["PeriscopeCore"],
),
unitTests(
name: "PeriscopeToolsTests",
bundleIdSuffix: "periscopetools",
productDependency: "PeriscopeTools",
sources: ["Shared/Periscope/PeriscopeTools/Tests/**"],
extraPackageProducts: [
"PeriscopeCore",
"PeriscopeUI",
],
),
unitTests(
name: "SwiftDataInspectorTests",
bundleIdSuffix: "swiftdatainspector",
Expand Down Expand Up @@ -404,6 +427,9 @@ let project = Project(
"LifecycleKitTests",
"LogKitTests",
"LogViewerUITests",
"PeriscopeCoreTests",
"PeriscopeUITests",
"PeriscopeToolsTests",
"SwiftDataInspectorTests",
"RegionKitTests",
"WhereCoreTests",
Expand All @@ -420,6 +446,9 @@ let project = Project(
"LifecycleKitTests",
"LogKitTests",
"LogViewerUITests",
"PeriscopeCoreTests",
"PeriscopeUITests",
"PeriscopeToolsTests",
"SwiftDataInspectorTests",
"RegionKitTests",
"WhereCoreTests",
Expand All @@ -435,6 +464,9 @@ let project = Project(
testScheme(name: "LifecycleKitTests"),
testScheme(name: "LogKitTests"),
testScheme(name: "LogViewerUITests"),
testScheme(name: "PeriscopeCoreTests"),
testScheme(name: "PeriscopeUITests"),
testScheme(name: "PeriscopeToolsTests"),
testScheme(name: "SwiftDataInspectorTests"),
testScheme(name: "RegionKitTests"),
testScheme(name: "WhereCoreTests"),
Expand Down
82 changes: 82 additions & 0 deletions Shared/Periscope/PeriscopeCore/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# PeriscopeCore – Module Shape

PeriscopeCore is the core of the **Periscope** observability framework: typed
`Codable` log events, the `Log<Event>` scope hierarchy, tags, spans, the sink
pipeline, ambient event sources, and the SwiftData store. See
[`README.md`](README.md) for the narrative and API.

This file complements the root [`AGENTS.md`](../../../AGENTS.md), which owns
the build system, formatting, and global conventions. Read that first.

## Layout

`Sources/` groups by concern: `Events/` (the event vocabulary — levels,
tags, attachments), `Loggers/` (`Log<Event>` and the scope tree),
`Context/` (task-local and per-instance context derivation), `Spans/`
(timing and exits), `Pipeline/` (the `Periscope` system, records, sinks),
`Store/` (SwiftData persistence and queries), and `Ambient/` (environmental
event sources). Tests stay flat, named 1:1 with their source files.

## Scope & dependencies

- **Foundation + os + SwiftData + Network only** (plus the ObjectiveC
runtime, solely for `LogContextProviding`'s deallocation trackers). No
SwiftUI, no app code, no LogKit. UIKit is allowed **only** inside
`#if canImport(UIKit)` (ambient sources, the image-attachment
convenience).
- Layering: `PeriscopeUI` and `PeriscopeTools` depend on this module — never
the reverse.

## Invariants

- **Emitting never blocks the caller.** Log calls append to a lock-guarded
buffer synchronously; sinks (OSLog, SwiftData) drain asynchronously, and
sink delivery order is emission order with scope definitions first.
Live streams see buffered order too: observer yields happen *under* the
state lock (yields only buffer) — yielding outside it would let racing
emitters invert live delivery, e.g. a span's end before its began.
- **Scope IDs are deterministic** (hash of parent + name) — the same path is
the same scope across processes and launches; `begin`/`end` span pairing
and cross-layer links rely on this.
- **Persistence must retain the full hierarchy** — events reference scopes
many-to-many (links), and scopes keep their parent chain.
- **Custom levels are values, not cases.** `LogLevel` is a struct ordered by
`severity`; never switch exhaustively over "all" levels.
- **Sink failures never propagate or vanish** — the store logs them to
OSLog, counts them, and persists a synthetic `StoreWriteFailed` marker
for the lost batch; the pipeline reports drops with a synthetic
`DroppedEvents` record.
- **A failed store save must roll back** (`recoverFromFailedWrite`):
the context is discarded, row caches drop, and the session row refetches
by identity — one poisoned batch must never wedge subsequent saves or
fork the session.
- **Payloads persist as versioned JSON** (`eventName` + `eventVersion`), not
per-event schemas — changing an event's shape must not require a SwiftData
migration.
- **Every span eventually ends, and its began is delivered first.**
`measure` closes on every path (including throw/cancellation); bounded
spans expire via the watchdog; re-begins supersede rather than refuse;
relaunch orphan-closes `endsWithProcess` spans. Don't add a span path
that can leave `openSpans` growing forever (`survivesRelaunch` resume is
the one staged exception — see [`TODOs.md`](../TODOs.md)). Registration
and the `SpanBegan` record land atomically (`LogRecorder.beginSpan`), so
a span is never closable before its began is in the pipeline; the
overflow drop policy never splits a recorded pair
(`LogEvent.isProtectedFromDropping`, an event-type opt-in the span pair
events set); and redaction is transform-only
for pair records (suppression falls back to a stripped copy). Keep all
three.
- **Span pairs floor together.** The floor decision is made once, at
begin, and the whole lifecycle follows it (`OpenSpan.beganRecorded`,
`LogRecord.bypassesFloors`): a recorded began always gets its end —
normal, expired, or superseded — even if floors rise mid-span, and a
floored began silences the entire span (overdue sentinel included).
Never a dangling half.

## Testing

Swift Testing in [`Tests/`](Tests), hosted in `StuffTestHost`
(`PeriscopeCoreTests`). Use in-memory stores, fresh `Periscope` systems per
test (never the shared singleton), and injected clocks. Note that
`Log<Event>()` defaults to `.shared` — a deliberate ergonomics exception to
the no-Core-defaults rule — so tests must always pass `system:` explicitly.
Loading
Loading