Skip to content
This repository was archived by the owner on May 1, 2026. It is now read-only.

fix(init): silence stray clack writes during channel add step#579

Merged
WcaleNieWolny merged 1 commit into
mainfrom
fix/init-channel-add-stray-logs
Apr 9, 2026
Merged

fix(init): silence stray clack writes during channel add step#579
WcaleNieWolny merged 1 commit into
mainfrom
fix/init-channel-add-stray-logs

Conversation

@WcaleNieWolny
Copy link
Copy Markdown
Contributor

@WcaleNieWolny WcaleNieWolny commented Apr 9, 2026

Summary

When picking "Use production" in the channel-add step of capgo init, the Ink onboarding UI broke and the "🚀 Capgo OTA Onboarding" header was repainted twice. The root cause was the same class of bug fixed in #560: helpers transitively called from addChannelInternal(silent: true) were still writing directly to process.stdout via @clack/prompts, bypassing Ink and corrupting the frame.

This PR continues the whack-a-mole pattern from #560 — no interceptor, no monkey-patching process.stdout — by threading an optional silent flag through the offending helpers and gating every direct clack call site behind it.

Changes

  • src/utils.ts
    • getConfig, getLocalConfig, getRemoteConfig, createSupabaseClient now accept an optional silent and propagate it through the call chain.
    • sendEvent is fire-and-forget telemetry — it now always loads remote config silently and only emits the LogSnag failure log when verbose is set, so it can never bypass an Ink-controlled stdout (e.g. via markStepmarkSnagsendEvent during init).
  • src/channel/add.ts
    • addChannelInternal now forwards its existing silent flag to getConfig, createSupabaseClient, and findUnknownVersion (the latter already supported silent but was being called without it).

All existing call sites continue to work — the new parameters default to false.

Test plan

  • npm run typecheck (tsc --noEmit) — clean
  • npx eslint src/utils.ts src/channel/add.ts — clean
  • npm run build (tsc && bun build.mjs) — built CLI and SDK successfully
  • Manual: run capgo init end-to-end on a fresh Capacitor project, pick "Use production" at the channel step, and confirm the Ink frame stays intact (no duplicated header) all the way through to step 3.
  • Manual: run capgo channel add <name> <appId> --default outside of init and confirm normal (non-silent) clack output is unchanged.

Summary by CodeRabbit

  • New Features

    • Added silent mode for configuration and client operations to suppress informational logging when needed.
  • Bug Fixes

    • Refined telemetry logging to only display error details when verbose mode is enabled, reducing log noise.

addChannelInternal is invoked from `capgo init` with silent=true while
the Ink onboarding UI owns stdout. Several helpers it transitively reaches
still wrote directly to process.stdout via @clack/prompts, which broke
the Ink frame and caused the 'Capgo OTA Onboarding' header to be repainted
twice when the user picked 'production' in the channel step.

Continuing the whack-a-mole pattern from #560, thread an opt-in silent
flag through the offending helpers and gate every clack write with it:

- getConfig, getLocalConfig, getRemoteConfig, createSupabaseClient now
  accept silent and propagate it through the call chain.
- sendEvent always loads remote config silently and only logs telemetry
  failures when verbose is set, since it is invoked in fire-and-forget
  contexts (including init markStep -> markSnag).
- addChannelInternal forwards its existing silent flag to getConfig,
  createSupabaseClient, and findUnknownVersion.

No interceptor / monkey-patching: each fix is at the actual call site.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2321c558-6c5f-48eb-b8b0-0a564c2e4903

📥 Commits

Reviewing files that changed from the base of the PR and between 0be32f8 and b788b83.

📒 Files selected for processing (2)
  • src/channel/add.ts
  • src/utils.ts

📝 Walkthrough

Walkthrough

This PR adds an optional silent parameter to configuration and utility functions, allowing callers to suppress informational and error logging while preserving exception handling. The addChannelInternal function threads this flag through downstream config loading, Supabase client creation, and version lookup calls.

Changes

Cohort / File(s) Summary
Silent flag threading
src/channel/add.ts
Threads silent parameter through getConfig(silent), createSupabaseClient(..., silent), and findUnknownVersion({ silent }) calls within addChannelInternal.
Silent parameter and conditional logging
src/utils.ts
Adds silent: boolean = false parameter to getConfig(), getLocalConfig(), getRemoteConfig(), and createSupabaseClient(). Functions suppress log.error/log.info output when silent is true; propagates flag through call chain. sendEvent() now calls getRemoteConfig(true) for silent remote config fetch and logs telemetry errors only when verbose is enabled.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A whisper through the code we weave,
Silent flags that log reprieve,
Errors still cry loud and true,
But chatter fades from morning's dew. ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/init-channel-add-stray-logs

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

@WcaleNieWolny WcaleNieWolny merged commit dcad60f into main Apr 9, 2026
15 of 16 checks passed
WcaleNieWolny added a commit that referenced this pull request Apr 9, 2026
addEncryptionStep was calling createKeyInternal({ force: true }, false)
which, in non-silent mode:

1. Writes to stdout directly via clack's intro(), log.*, and pConfirm
   (the exact whack-a-mole gating problem PR #560/#579 fixed for the
   rest of the init flow — it renders garbled output when ink is also
   drawing).
2. Internally calls promptAndSyncCapacitor with validateIosUpdater=true,
   which throws 'iOS sync validation failed. Delete your iOS folder…'
   and bubbles up as 'Error during onboarding' on any mis-synced iOS
   project.

Step 7 (buildProjectStep) already runs `cap sync` with proper iOS
recovery via handleBrokenIosSync, so a second sync inside the
encryption step is strictly redundant.

Pass silent=true and setupChannel=false to skip all of the above:
keys get written, config is updated, and the ink-managed spinner
stays as the only thing drawing to the screen. Also wrap the call
in try/catch so the existing 'retry / skip' recovery path actually
fires on failure (createKeyInternal throws rather than returning
false on errors, so the old `if (keyRes)` else branch was dead).
WcaleNieWolny added a commit that referenced this pull request Apr 10, 2026
* feat(init): persist encryption outcome as summary panel on step 6

Previously, the tail of addEncryptionStep pushed 3-7 pLog.info lines
(outcome bullets for enabled / skipped / failed paths) that were
immediately wiped when the next step's renderInitOnboardingFrame
called clearInitLogs() — they only appeared as a flash.

Mirror the step-4 code-diff panel pattern: introduce
InitEncryptionSummary state + setInitEncryptionSummary runtime setter
and render an EncryptionSummaryPanel (green border when enabled,
yellow when skipped / failed) that survives the step-5→step-6 frame
transition.

The summary is built in addEncryptionStep based on the final decision
(enabled / user skipped / key creation failed) and cleared in the
main onboarding loop after step 6 completes, so it shows as a
persistent 'what happened' callout on the Select Platform screen.
Also persisted into the resume JSON and restored on resume-at-step-6
so the panel survives interrupted sessions.

Follow-up fixes included:
- hide 'What is encryption? (learn more)' option after user has
  already viewed the overview (re-offering it made no sense)
- always print the docs URL as part of the overview (so users can
  copy it even if they decline to open their browser right now)
- use an embedded newline before the docs URL instead of a separate
  empty log entry (Ink collapses empty Text to zero height)
- use canonical '${pm.runner} @capgo/cli@latest key create' rather
  than a non-existent 'capgo key create' shortcut in the summary

* fix(init): drop redundant second encryption prompt

Q1 already labels its yes branch as 'Yes — set up end-to-end
encryption', so the follow-up 'Do you want to use encryption for X?'
select with the same yes/no options was just re-asking the same
question. The 3 intermediate info lines ('Capgo bundles are web
assets...' / 'That is why we recommend encryption...' / 'Do not put
private API keys...') were also redundant — learn-more already
covers the rationale and the summary panel covers the warning.

Critical answers at Q1 now go straight into key creation.

* fix(init): run key creation in silent mode to avoid ink/clack collision

addEncryptionStep was calling createKeyInternal({ force: true }, false)
which, in non-silent mode:

1. Writes to stdout directly via clack's intro(), log.*, and pConfirm
   (the exact whack-a-mole gating problem PR #560/#579 fixed for the
   rest of the init flow — it renders garbled output when ink is also
   drawing).
2. Internally calls promptAndSyncCapacitor with validateIosUpdater=true,
   which throws 'iOS sync validation failed. Delete your iOS folder…'
   and bubbles up as 'Error during onboarding' on any mis-synced iOS
   project.

Step 7 (buildProjectStep) already runs `cap sync` with proper iOS
recovery via handleBrokenIosSync, so a second sync inside the
encryption step is strictly redundant.

Pass silent=true and setupChannel=false to skip all of the above:
keys get written, config is updated, and the ink-managed spinner
stays as the only thing drawing to the screen. Also wrap the call
in try/catch so the existing 'retry / skip' recovery path actually
fires on failure (createKeyInternal throws rather than returning
false on errors, so the old `if (keyRes)` else branch was dead).

* fix(init): suppress key-creation success flash and add dev helpers

- Stop the key-creation spinner without a success message. Passing a
  message routes it through pushInitLog into the rolling log buffer,
  which renderInitOnboardingFrame wipes when step 6 renders — producing
  a visible "Keys created 🔑" flash. The persistent EncryptionSummaryPanel
  already surfaces the outcome on step 6, so nothing is lost.
- Add scripts/remove-encryption-key.mjs: TEST-ONLY helper that deletes
  .capgo_key{,_v2}{,.pub} and scrubs publicKey/privateKey from both
  capacitor.config.json and capacitor.config.{ts,js} so the encryption
  step can be re-run from a clean slate. Supports --show and --dry-run.
- Track scripts/reset-onboarding-step.mjs and
  scripts/remove-notify-app-ready.mjs, the sibling dev helpers used for
  iterating on the onboarding wizard.

* feat(init): stream cap sync output after key creation in step 5

Instead of deferring the native sync to step 7 and showing a
misleading "Encryption ENABLED" panel, we now run `cap sync` right
inside step 5 with a full-screen streaming output panel. This mirrors
the log-streaming UX from the builder onboarding.

Changes:
- Add InitStreamingOutput state + streaming panel to runtime/UI. When
  active it takes over the entire viewport (header + tail-view of
  stdout/stderr lines + spinner/status footer).
- Add streamCommandInInitPanel() helper that spawns a child process
  with piped stdio and feeds lines into the streaming panel.
- After createKeyInternal succeeds, stream `cap sync` (no platform —
  syncs all native projects) so the public key is actually bundled
  before we claim encryption is enabled.
- 3.5s dwell after sync completes so the user can read the result.
- On sync success → green "Encryption ENABLED" summary.
- On sync failure → yellow "pending-sync" fallback; step 7 build+sync
  still gets a chance to promote via promoteEncryptionSummaryToEnabled.
- Color-code streaming lines by prefix: green for success markers,
  red for errors, yellow for warnings, cyan for info, blue for
  [capacitor] lines, dim gray for indented file paths.
- Remove the "debugging update failures is harder" bullet from the
  enabled summary — it was pointless.
- Bump post-sync dwell from 1.5s to 3.5s.

* chore: remove dev-only remove-encryption-key script from PR

Keep it locally but out of the tracked tree — it is a personal
test helper, not something that should ship.

* chore: remove dev-only scripts from PR

Keep them locally but out of the tracked tree — they are personal
test helpers, not something that should ship.

* fix(init): avoid global Buffer reference to satisfy lint rule
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant