Skip to content

V0.1.3/m2/webview UI#7

Open
Vedansi18 wants to merge 51 commits into
hi0001234d:mainfrom
Vedansi18:v0.1.3/m2/webview-ui
Open

V0.1.3/m2/webview UI#7
Vedansi18 wants to merge 51 commits into
hi0001234d:mainfrom
Vedansi18:v0.1.3/m2/webview-ui

Conversation

@Vedansi18
Copy link
Copy Markdown
Collaborator

Adds the decision-session UI surface of the VS Code extension — modules
M6 (WebviewViewProvider), M7 (HTML template), and M8 (prompt
injection)
per dev plan §3 M2 §2.2. The webview renders advisory +
options inside Cursor / Windsurf's activity-bar panel, auto-reveals when
a DecisionSessionPayload arrives, and round-trips the user's selection
back to the host's chat input (via clipboard + toast for now; B4 will
plug in a direct-injection primary path via the injectFn contract this
branch sets up). No Layer C file is touched.

Stacked on M2 Branch 1 (v0.1.3/m2/extension-skeleton, commit
879ed5e) per the dependency table in dev plan §3.0. Does NOT depend on
M2/B2 — webview UI is parallelisable with chat-history capture.

Modules covered (per dev plan §3 M2 §2.2)

Module File(s) What it does
M6 — View provider src/webview/view-provider.ts NexpathDecisionSessionViewProvider implements vscode.WebviewViewProvider. resolveWebviewView wires enableScripts + localResourceRoots +
initial HTML + onDidReceiveMessage + onDidDispose. publishPayload stores the payload, replaces HTML, and calls webviewView.show(true) for the auto-reveal UX matching architecture rev 2 §4 /
discussion log §2 Correction 9. Payload survives view dispose / re-show. Message handler catches onSelect errors so they never become unhandled rejections (added in audit follow-up).
M7 — HTML template src/webview/html.ts Pure function renderDecisionSessionHtml(payload, opts) — no I/O. Returns full self-contained HTML for the webview. Two modes: empty/watching state
(no scripts) and populated state (advisory + numbered option buttons + dismiss). CSP default-src 'none' with nonce-scoped scripts. All user-controlled strings HTML-escaped including attribute-breaking
" (</>/&/"/'). Theming via --vscode-* CSS variables — inherits the host's theme automatically.
M8 — Prompt injection src/webview/prompt-injection.ts handleOptionSelection(text, deps) with TWO paths: (1) primary — deps.injectFn(text) if provided and returns true; (2) fallback —
vscode.env.clipboard.writeText + info toast directing user to paste. injectFn is the load-bearing contract for Branch 4 to fill in per agent (see memory
project_b4_prompt_injection_contract.md).

Layer C UX consistency

After reading src/decision-session/TtySelectFn.ts (Layer C — DO NOT MODIFY),
the following UX patterns from the existing TTY decision-session were
mirrored into the webview:

TTY pattern Webview behaviour
Ctrl+X opt-out (TtySelectFn.ts:128) Webview keydown handler dispatches {type: 'dismiss'}
Standard cancel Esc also dispatches {type: 'dismiss'}
Numbered options Visible 1. 2. … prefixes + keys 19 dispatch select
First option focus optionButtons[0].focus() on render
Discoverable hints Visible (press 1–N, or Esc / Ctrl+X to dismiss) hint in the options header

Not mirrored (intentional): TTY's two-step "Send now / Copy to clipboard"
sub-menu — until B4's injectFn lands, every path ends in clipboard
anyway, so the two-step adds friction without value. TTY's 60s
auto-dismiss timeout — webview is non-modal, the user can let it sit.

Files

Path Type Tests
src/ext-vscode/src/webview/view-provider.ts M6 16
src/ext-vscode/src/webview/view-provider.test.ts tests
src/ext-vscode/src/webview/html.ts M7 19
src/ext-vscode/src/webview/html.test.ts tests
src/ext-vscode/src/webview/prompt-injection.ts M8 10
src/ext-vscode/src/webview/prompt-injection.test.ts tests
src/ext-vscode/src/extension.ts (modified) wiring +5 tests in extension.test.ts
src/ext-vscode/src/extension.test.ts (modified) tests
src/ext-vscode/package.json (modified) manifest — view type webview, dropped viewsWelcome (rendered inside the webview now)

50 new unit tests added in this branch. Sub-package total: 75 tests
across 6 files (was 25 in B1).

Test plan

Automated

# Root + sub-package typecheck
npx tsc --noEmit                          # root
(cd src/ext-vscode && npx tsc --noEmit)   # sub-package — both EXIT=0

# Sub-package tests in isolation
(cd src/ext-vscode && npx vitest run)
# expected: 75/75 pass across 6 test files

# Full root test suite (vitest picks up sub-package via vi.mock('vscode'))
npm test
# expected: 1901 passing + 18 pre-existing TtySelectFn carry-forward

# Extension bundle still builds
(cd src/ext-vscode && npm run build)
# expected: out/extension.js produced (~12.3 KB — was 3.4 KB in B1)

Manual

End-to-end against a real Cursor / Windsurf is the Branch 5 smoke-test
gate, not this PR. For this branch, manual verification is unit-driven
- esbuild bundle inspection.

To eyeball the webview HTML output:

cd src/ext-vscode
node --input-type=module -e "
import { renderDecisionSessionHtml } from './out/webview/html.js';
console.log(renderDecisionSessionHtml(
  { advisory: 'Consider clarifying scope.', options: [
      { id: 'a', label: 'Refine the request' },
      { id: 'b', label: 'Proceed with caution' },
  ]},
  { cspSource: 'vscode-resource:test' }
));
"
# expected: full HTML printed — advisory + 2 numbered buttons + dismiss button + keyboard hint

hi0001234d and others added 30 commits May 5, 2026 20:43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nt sets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ofile

Adds selectAbsenceMap helper (hardcore_pro → formal, else → casual) and
updates resolveDecisionContent to use it for non-vibe profiles. Updates
6 no-profile tests and 2 priority-override tests to assert casual variants,
consistent with selectNonBeginnerVariant's undefined → casual behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 3 hardcore_pro routing tests for remaining formal absence variants,
add all 8 non-beginner absence sets to allContent and per-set count tests,
add 4 beginner absence sets to C-02 structural validation block with
missing imports for ABSENCE_REGRESSION_CHECK_BEGINNER and
ABSENCE_SPEC_ACCEPTANCE_BEGINNER.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes to buildMjsScript() in TtySelectFn.ts:
- Fix _lineCount to split labels by \n and compute per-line visual rows
  instead of treating the whole label as one long string; this correctly
  estimates height for multi-line (numbered-steps) option labels
- Add wrapping guard: when all options fit within budget (no-overflow) but
  their total visual lines exceed the option count, recompute _maxItems
  using the option count as a tight budget ceiling so visually dense
  option sets always get a viewport rather than a flat list
- Pass maxItems to select() so @clack/prompts k() uses _maxItems as the
  viewport window instead of rows-4; without this, maxItems computation
  had no effect on the actual rendered UI

Applies to Mac, Windows, and Linux new-window paths (all share buildMjsScript).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…word lists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ded vibeKeyword sets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds formal, casual, and beginner DecisionContent objects for
idea_scoping, idea_constraint_check, and idea_user_definition.
Includes content existence tests covering shape and non-empty strings.
Map wiring deferred to Phase 4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds formal, casual, and beginner DecisionContent objects for
task_ordering, task_sizing, and task_definition_of_done.
Includes content existence tests covering shape and non-empty strings.
Map wiring deferred to Phase 4.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
hi0001234d and others added 21 commits May 7, 2026 18:03
Adds formal, casual, and beginner DecisionContent objects for
user_feedback_review and iteration_planning. Wires all 8 new signal
keys (Groups A, B, C) into ABSENCE_CONTENT, ABSENCE_CONTENT_CASUAL,
and ABSENCE_CONTENT_BEGINNER. Adds 24 routing tests covering all
3 registers for all 8 signals, plus Group C content existence tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…INNER

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cture test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…NG_CASUAL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 8 new absence signal definitions in signals.ts (scope_creep, context_loss, api_design_review, accessibility, environment_and_secrets, data_validation, ci_pipeline, rate_limiting)
- 24 new content set constants across 3 registers (formal, casual, beginner) in options.ts and options-beginner.ts
- All 8 keys added to ABSENCE_CONTENT, ABSENCE_CONTENT_CASUAL, ABSENCE_CONTENT_BEGINNER maps
- relevantProjectTypes filter on api_design_review, accessibility, rate_limiting signals
- AbsenceDetector.ts: Gate 3 now uses per-signal absenceThreshold with profile multiplier; project-type gate added
- types.ts: relevantProjectTypes field added to SignalDefinition
- auto.ts: projectType extracted from getProject() and passed to detectAbsenceFlags
- Tests: routing, content existence, structure, and buildOptionList coverage for all 8 new signals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Branch 1 of Milestone M2 (v0.1.3/m2/extension-skeleton). Establishes the
src/ext-vscode/ sub-package with an esbuild-driven build pipeline
(ESM source -> CJS bundle for the VS Code host), activates on
onStartupFinished, and ships the four scoped modules:

  M1 - Skeleton: package.json (activationEvents, activity-bar container +
       placeholder view backed by viewsWelcome so the icon actually renders),
       tsconfig.json, esbuild.config.mjs, src/extension.ts entrypoint.
  M5 - IPC stub: src/ipc.ts. spawnAuto(prompt, sessionId) and
       spawnStop(sessionId) spawn the nexpath CLI as subprocesses and parse
       the decision-session JSON payload from stdout, with typed errors
       (NexpathBinaryNotFoundError, NexpathMalformedPayloadError) and
       configurable binary-path resolution
       (opts.binaryPath -> NEXPATH_BIN env -> 'nexpath' on PATH).
       The exact stdin envelope vs. Layer C input contract is intentionally
       a stub here; Branch 4 (cursor-windsurf-adapters) finalises it.
  M11 - Onboarding: src/onboarding.ts. First-launch consent toast persists
        the user's choice to globalState; on macOS, additionally shows a
        Full-Disk-Access guidance toast that deep-links to the System
        Settings privacy pane.
  M12 - Icon: media/icon.svg. Y-fork (branching path) representing
        "next path" decision points; monochrome currentColor, scalable.

25 unit tests co-located alongside source (8 onboarding, 11 ipc, 6 extension),
runnable via root vitest with vi.mock('vscode') stubs. Sub-package has its
own tsconfig + package-lock; root tsconfig now excludes src/ext-vscode/ so
each side owns its TS build. Both root and sub-package tsc --noEmit are
clean. Full root test suite: 1851 passing + 18 pre-existing unrelated
TtySelectFn Windows-sim failures (carried forward from dev plan §3.0).

Deferred (flagged for follow-up, not blockers for this branch):
- 5 moderate npm-audit warnings in the esbuild -> vite -> vitest dev chain
  (dev-only; will be addressed during M5 hardening).
- IPC stdin envelope contract: real wiring lands in Branch 4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Branch 3 of Milestone M2 (v0.1.3/m2/webview-ui). Stacked on M2 Branch 1
(commit 879ed5e) — does NOT depend on M2 B2's watcher, only on B1's
skeleton + the DecisionSessionPayload type from ipc.ts. Delivers the
three scoped modules:

  M6 — WebviewViewProvider: src/webview/view-provider.ts.
       NexpathDecisionSessionViewProvider implements vscode.WebviewViewProvider
       for the nexpath.status activity-bar view. resolveWebviewView wires
       webview.options (enableScripts + localResourceRoots), sets initial
       HTML, registers onDidReceiveMessage + onDidDispose. publishPayload
       stores the payload, updates the HTML, and calls webviewView.show(true)
       for the auto-reveal UX matching architecture rev 2 §4. Payload
       survives view dispose/re-show. Injectable onSelect dependency for
       tests. Exposes getCurrentPayload() + handleMessage() for direct
       message-routing tests.

  M7 — HTML template: src/webview/html.ts.
       renderDecisionSessionHtml(payload, opts) — pure function, no I/O.
       Returns the full self-contained HTML for the webview. Two modes:
       empty/watching state (no scripts, just "Nexpath is active…") and
       populated state (advisory + numbered option buttons + dismiss).
       CSP: default-src 'none' with nonce-scoped scripts. All user-controlled
       strings HTML-escaped. Theming via --vscode-* CSS variables so the UI
       inherits Cursor/Windsurf's theme. Tests verify both states, nonce
       handling, HTML escaping (incl. <script> + onerror= injection attempts),
       and empty-options array.

  M8 — Prompt injection: src/webview/prompt-injection.ts.
       handleOptionSelection writes the selected option text to the system
       clipboard via vscode.env.clipboard.writeText + shows a non-modal info
       toast directing the user to paste. This is the ONLY reliable path —
       VS Code text-editing APIs target editor documents, not the host's
       (Cursor's) chat input panel (dev plan §2.4). Branch 4 may discover
       a Cursor-specific command that lets us write directly; until then
       clipboard + toast is the primary path. Injectable deps for tests.

extension.ts updates:
  - Registers the view provider on activate via
    vscode.window.registerWebviewViewProvider(VIEW_ID, instance).
  - Pushes the registration disposable onto context.subscriptions for
    cleanup on deactivate.
  - Holds the provider at module scope; exposes via getViewProvider() so
    Branch 4's adapter wiring can publish payloads.
  - View provider registration runs BEFORE onboarding so the icon shows
    immediately on activation, even while consent toasts are open.
  - Onboarding errors still swallowed (per existing B1 behaviour).

package.json updates:
  - nexpath.status view now declares "type": "webview" (was implicit tree).
  - viewsWelcome entry removed — webview-type views render their own empty
    state from inside the webview HTML, not via viewsWelcome. The empty
    state in renderDecisionSessionHtml replaces it.

38 new unit tests:
  - html.test.ts: 13 (escapeHtml + empty state + populated state + nonce
    + HTML escaping in advisory/options + empty options array)
  - view-provider.test.ts: 14 (VIEW_ID + resolveWebviewView × 4 +
    publishPayload × 3 + clearPayload + handleMessage × 5)
  - prompt-injection.test.ts: 6 (clipboard write + toast + error paths +
    DI + empty string)
  - extension.test.ts: +5 (registration test + subscriptions push +
    getViewProvider + onboarding-rejects-but-view-still-registered + the
    deactivate clears viewProvider)

Sub-package totals at branch HEAD: 63 tests across 6 files (was 25 in B1,
+38 here). Root tsc + sub-package tsc clean. Full root test suite 1889
passing + 18 pre-existing TtySelectFn carry-forward (unrelated).
Esbuild bundle grew from 3.4 KB → 11.0 KB (includes the new webview
modules + their CSS template strings).

Deferred (flagged, not blockers for this branch):
- Pre-prompt blocking on Cursor/Windsurf — current architecture only
  shows guidance after the host sends the prompt. Pre-send blocking
  would need a keybinding hijack (architecture doc §11 open question 7).
- Cursor-specific "write to chat input" command — discover in Branch 4
  if it exists, otherwise clipboard + toast remains the only path.
- E2E test against a real Cursor instance — Branch 5 (smoke-test) gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two refinements after cross-confirmation review against the dev plan + a
read of the Layer C TTY UI in src/decision-session/TtySelectFn.ts.

## 1. injectFn contract — addresses Drift hi0001234d#3 (primary text-editing path)

prompt-injection.ts now defines:
  - `OptionInjector = (text: string) => Promise<boolean>` — the contract
    for a direct-injection function (agent-specific, lives in B4).
  - `PromptInjectionDeps.injectFn?` — optional adapter-supplied
    injector. B3 default is absent → clipboard fallback always wins.

handleOptionSelection now has two paths:
  1. If `deps.injectFn` is provided AND `injectFn(text)` resolves true:
     skip clipboard. Text is in the chat input. Done.
  2. Otherwise (injectFn absent, returned false, or threw): fall back
     to clipboard + info toast.

B4 (cursor-windsurf-adapters / M9 + M10) will:
  - Discover Cursor / Windsurf command ids that write text to the
    AI chat input (via `vscode.commands.getCommands(true)`).
  - Implement `cursorChatInputInject` / `windsurfChatInputInject` of
    type OptionInjector.
  - Pass them through the view-provider constructor's onSelect arg as:
      const onSelect = (text) =>
        handleOptionSelection(text, { injectFn: cursorChatInputInject });

Decision saved to memory at
~/.claude/projects/-home-emptyops-Documents-Vedanshi-NexPathMain-reviewduel/memory/project_b4_prompt_injection_contract.md
— marked load-bearing (do not delete or rename the named symbols).
This guarantees the deferred work doesn't get forgotten in a future
session.

4 new unit tests in prompt-injection.test.ts:
  - injectFn returning true → clipboard NOT touched
  - injectFn returning false → falls back to clipboard
  - injectFn throwing → falls back to clipboard
  - injectFn absent → clipboard path (default B3 behaviour)

## 2. Keyboard shortcuts — addresses Drift hi0001234d#2 (Layer C UX consistency)

After reading TtySelectFn.ts, the relevant UX patterns to mirror:
  - Ctrl+X = opt-out / dismiss (matches Layer C's `\\x18` keypress
    handler at TtySelectFn.ts:128 + the install disclosure copy:
    "press Ctrl+X during an advisory")
  - Esc = standard web cancel (TTY doesn't have this but it's
    expected web UX)

Added to the webview HTML script:
  - keydown handler for Ctrl+X → dispatches `{type: 'dismiss'}`
  - keydown handler for Esc → dispatches `{type: 'dismiss'}`
  - keydown handler for digits 1-9 → dispatches `{type: 'select'}`
    against the Nth option (matches the visible numbering)
  - First option focused on render so keyboard users land on
    something actionable.
  - Visible kbd-hint text in the options header and on the dismiss
    button so the shortcuts are discoverable.

Patterns NOT mirrored (intentional, rationale):
  - TTY's two-step "Send to Claude now" / "Copy to clipboard" sub-menu:
    redundant in the webview — until B4's injectFn lands, every path
    ends in clipboard anyway. The two-step adds friction without value.
  - 60s auto-dismiss timeout: the webview is non-modal; the user can
    let it sit indefinitely. Adds complexity without UX gain.
  - Arrow-key navigation (Tab already works natively in HTML; number
    keys are the faster path for our short option lists).

5 new unit tests in html.test.ts:
  - keyboard hint string visible in options header
  - hint range scoped to option count (capped at 9)
  - keydown handler dispatches select on digits 1-9
  - Esc + Ctrl+X handlers dispatch dismiss
  - first option button focused on render

## Verification

  - Sub-package tsc --noEmit clean
  - Sub-package vitest: 72/72 pass (was 63, +9 new)
  - Root tsc --noEmit clean
  - Full root test suite: 1898 passing + 18 pre-existing TtySelectFn
    carry-forward
  - Esbuild bundle: 11.0 KB → 12.3 KB (the new keyboard handler script
    + injectFn branch)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cross-confirmation audit caught one real resilience gap + two missing
unit-test coverage points. All scoped to M2/B3 work.

## Resilience fix in view-provider.ts

NexpathDecisionSessionViewProvider.handleMessage previously did:

    await this.onSelect(msg.optionLabel);

If onSelect rejected (which a real B4 injectFn can — e.g. when a Cursor
command is missing or throws), the rejection propagated up. The caller
chain is `webview.onDidReceiveMessage` → `void this.handleMessage(raw)`
in resolveWebviewView, which has no `await` to catch it — so it would
have surfaced as an unhandled promise rejection in the extension host.

Fixed by wrapping the onSelect call in try/catch + console.error. The
user-facing error path stays in handleOptionSelection (which already
shows a toast on clipboard failure); the catch here is a last-resort
guard so the extension host doesn't see unhandled rejections.

## 3 new unit tests covering previously-untested behaviour

view-provider.test.ts (+2):
  - "a second publishPayload replaces the first (no stacking)" — verifies
    the latest payload wins, both currentPayload and webview.html
    reflect it, view.show is called per publish.
  - "catches errors from onSelect so they never become unhandled
    rejections" — proves the new try/catch works + the error is logged
    to console.error with the right prefix.

html.test.ts (+1):
  - "escapes attribute-breaking quote characters in option id and label"
    — the existing escape test covered `<` `>` `&`. Quotes (`"`) inside
    a data-option-id="..." attribute would close the attribute and
    allow injection. Verifies escapeHtml correctly converts `"` to
    `&quot;` in both data-option-id and data-option-label.

## Verification

  - Sub-package tsc --noEmit clean
  - Sub-package vitest: 75/75 pass (was 72; +3)
  - Root tsc --noEmit clean
  - Full root test suite: 1901 passing + 18 pre-existing TtySelectFn
  - Esbuild bundle: still builds clean (~12.3 KB)

Closes the M2/B3 unit-test audit gap. Per auto-commit rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants