Skip to content
C.W.K.
Lesson 05 of 05 · published

Who Asked — Triggers, Activity and the Dropped Second Press

~12 min · trigger, activity, busy, status-line

Level 0Idle Hands
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Every trigger enters the same engine; this only remembers which door the request came through." — the doc comment on Trigger

A Label, Not a Path

Because every trigger runs the same command through the same engine, the trigger itself has nothing to do with behaviour. It is pure provenance: a small value with a kind — menu, hotkey, link, settings, door or system — and, for the app door, the caller's actor label as the family app-action kit admitted it (for example pippa:codex-pippa). The system kind is Tinker acting on its own behalf, which happens in exactly one place: restoring before it quits.

The Activity Log

Every finished request — success or failure — appends an ActivityEntry: a timestamp, the trigger, the action ("Apply Final Cut editing"), a detail ("3 changes", "already active") and an error if there was one. The log keeps the last 200 entries and drops the oldest beyond that, so it can never grow without end. Settings shows it newest first, and the app door serves it page by page through activity.list. Resume and Forget are recorded too; those are decisions Dad will want to find later.

"Already Active" Is Not an Error

The detail line distinguishes four outcomes: applied with n changes, already active, restored with n changes, and was not active. Counting changes uses the journal before and after, so a preset whose apps were all hidden already or not running is honestly reported: the detail reads "0 changes" and the status line "Final Cut editing applied (0 changes)". None of these are failures; they are what happened.

The Second Press

Hotkeys invite double presses. If a second press of the editing toggle arrived while the first was still applying and was queued, it would run after the first and flip editing mode straight back off. If it were refused with an error, the screen would show a red message for something Dad did not mean to do. Tinker does neither: performCommand drops a repeat while the model is busy, silently. The one command never dropped is Stop and restore everything — an emergency exit that can be swallowed is not an exit.

A Status Line That Doesn't Lie About Order

Commands finish before their Watchfire comparison arrives (track 8 explains why). If a second command starts in between, the first command's late summary must not overwrite the second's status. Each run takes a token; a summary is written to the status line only if its token is still the latest. Small, but it is the difference between a status line that describes the last thing you did and one that describes whatever finished last.

Code

Who asked, as a value·swift
public struct Trigger: Codable, Equatable, Sendable {
  public enum Kind: String, Codable, Sendable { case menu, hotkey, link, settings, door, system }
  public var kind: Kind
  /// The app door's caller as the Kit names it, such as `pippa:codex-pippa`.
  public var actor: String?
  public init(_ kind: Kind, actor: String? = nil) { self.kind = kind; self.actor = actor }
  public static func door(_ actor: String?) -> Trigger { Trigger(.door, actor: actor) }
}

public struct ActivityLog: Codable, Equatable, Sendable {
  public static let limit = 200
  public var schemaVersion = 1
  public var entries: [ActivityEntry] = []
  public mutating func record(_ entry: ActivityEntry) {
    entries.append(entry)
    if entries.count > Self.limit { entries.removeFirst(entries.count - Self.limit) }
  }
}
Drop a repeat while busy — except the emergency exit·swift
func performCommand(_ id: String, trigger: Trigger) async {
  let command = configuration.commands.first(where: { $0.id == id })
  // A repeated press while a command runs is dropped, not kept on screen as
  // an error. Stop and restore everything is never dropped.
  if busy, let command, command.operation != .releaseAll { return }
  await attempt {
    guard let command else { throw TinkerError("Unknown command: \(id).") }
    _ = try await self.run(command.operation, commandID: id, trigger: trigger)
  }
}
A late summary only lands if it is still the latest·swift
messageToken &+= 1
let token = messageToken
// ... the command runs and finishes ...
return Task {
  let summary = outcome + " · " + await comparison()
  if token == self.messageToken { self.message = summary }   // stale? stay quiet
  return summary
}

External links

Exercise

Design an activity entry for a request Pippa sends through the app door to apply the broadcast preset, which turns out to be already active. Write the trigger, action, detail and error fields, and say how it would appear in Settings. Then decide: should a request that changed nothing be recorded at all?
Hint
Trigger kind door with actor like pippa:<her harness label>, action 'Apply Broadcast', detail 'already active', no error; Settings would show 'App door (pippa:…)'. Recording it is right: a no-op request is still a request, and 'Pippa asked while it was already on' is exactly the kind of fact that explains later confusion.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.