Skip to content
CSA Loom — the Microsoft Fabric experience for Azure tenants where Fabric isn't yet available: lakehouses, warehouses, notebooks, semantic models, Activator rules, Data Agents, across Commercial, GCC, GCC-High, and DoD IL5

learning-hub-copilot — parity with an in-product tutorial assistant (Fabric "Help + Copilot" / Azure portal guided tutorials)

Source UI: Microsoft Fabric in-product Copilot + Learn pane (https://learn.microsoft.com/fabric/get-started/copilot-fabric-overview), Azure portal guided tutorials / "Diagnose and solve problems" (https://learn.microsoft.com/azure/azure-portal/). Backed entirely by CSA Loom's own Azure-native services — no Microsoft Fabric / Power BI dependency (see .claude/rules/no-fabric-dependency.md). Works with LOOM_DEFAULT_FABRIC_WORKSPACE UNSET.

Goal

A per-step-aware Help Copilot that (1) knows which tutorial step the user is on, (2) reads the open item's run / provision receipts to auto-detect why a step failed, and (3) recommends or applies a fix — recommendations from the receipt's gate.remediation, in-editor edits via an approval-gated Keep/Undo diff, and actions via handoff to the cross-item Copilot at /copilot.

Feature inventory (real source-UI capabilities) → Loom coverage → backend

Capability (source UI) Loom coverage Backend per control
Copilot is aware of the screen / object you're on ✅ built (pre-existing pageContext) injected system message in orchestrateHelp
Copilot is aware of the tutorial step you're on ✅ built pageContext.tutorial → 3rd system message; csaloom:tutorial-step CustomEvent from the LearnPane stepper
Step-by-step guided walkthrough with a "help" affordance ✅ built LearnPane stepper (item-side-panel.tsx) — per-step "Help with this step" button, content sourced from lib/learn/content.ts (no free-form authoring)
Tutorial step resolves the item to diagnose even off-route ✅ built receiptScopeFromTutorialId('editor:<type>#<id>') (tutorial-scope.ts) — when the route hasn't put the item id in the path, the widget derives receiptScope from the active editor tutorial's id so readReceipts still targets the right item
Auto-detect errors from run history ✅ built readReceipts tool → gatherReceipts(source:'runs') → ADF listPipelineRuns/listActivityRuns (Azure-native); honest gate via adfConfigGate()
Auto-detect errors from install/provision state ✅ built readReceipts(source:'provisioning') reads Cosmos state.provisioning (status / gate.reason / gate.remediation / gate.link / error)
Auto-detect from recent activity/audit history ✅ built readReceipts(source:'audit') queries auditLogContainer() (same query as the audit route)
Recommend a fix ✅ built the agent leads with the receipt's gate.remediation; each receipt becomes a Citation
Apply an in-editor fix (gated) ✅ built proposeFix tool → __proposedChange__ sentinel → proposed_change step → CopilotDiff Keep/Undo → applyChange(target) bridge. Target constrained to notebook-cell:<id> — the only surface with a registered bridge today (notebook-editor.tsx). Query/SQL/KQL editors are not yet wired for in-place apply, so proposeFix rejects non-notebook-cell: targets and the agent hands those off to /copilot instead — no dead control
Apply a fix that needs an ACTION (re-provision / re-run) ✅ built existing handoff → /copilot (the cross-item action orchestrator) — not duplicated here
Cite the source the answer reasoned over ✅ built docs/repo RAG citations (pre-existing) + receipt citations (new)
Honest config gate when a backend isn't wired ⚠️ honest-gate runDiagnostic (AOAI/Search/Cosmos) + readReceipts runs-gate naming the exact missing ADF env var

Zero ❌. Zero stub banners. Every control calls a real backend or surfaces an honest infra gate.

Apply-fix safety contract

  • The mutation fires only on the user's explicit Keep in the Monaco diff, never automatically (mirrors the cross-item Copilot's approval-diff contract; apply-change.ts).
  • proposeFix targets are deterministic keys only (notebook-cell:<id>) — no free-form, user-authored mutation paths (.claude/rules/loom-no-freeform-config.md). query-editor:<id> is a reserved key the generic bridge registry (apply-change.ts) can route, but no query editor registers it yet, so proposeFix rejects it rather than advertising a control that cannot apply.
  • When the owning editor has closed, applyChange returns false and the widget says so honestly rather than pretending the edit applied (.claude/rules/no-vaporware.md).

Per-cloud note (Azure-native default)

Run receipts use the Azure Data Factory monitoring REST (listPipelineRuns / listActivityRuns) which the run routes already gate on (LOOM_SUBSCRIPTION_ID / LOOM_DLZ_RG / LOOM_ADF_NAME). The Fabric pipeline run API is never called. Provisioning + audit receipts live in Cosmos and are cloud-agnostic. AOAI scope resolution goes through the existing resolveAoaiTarget → env, honoring AZURE_CLOUD.

bicep + bootstrap sync

No new Azure resource, env var, role assignment, or Cosmos container. readReceipts reuses the already -provisioned items + audit-log Cosmos containers and the ADF env vars the run routes already require. The help agent's model deployment resolves through the existing tenantConfig.helpAgentDeployment → resolveAoaiTarget. Therefore there is nothing to add to platform/fiab/bicep/ for this feature.

Verification

  • npx tsc --noEmit — clean for all touched files (pre-existing griffel style-token backlog excluded).
  • npx vitest run lib/azure/__tests__/help-receipts.test.ts lib/azure/__tests__/help-copilot.test.ts — 32/32 green: receipt shaping over provisioning/audit/ADF runs (incl. honest gate + error surfacing), the readReceipts tool (remediation gate + run citation + no-scope error), and the proposeFix tool (sentinel round-trip + deterministic-target rejection).
  • npx vitest run lib/components/help-copilot/__tests__/tutorial-scope.test.ts — covers receiptScopeFromTutorialId: parses editor:<type>#<id> (incl. guid item ids), returns undefined for an unsaved #new item, non-editor tutorial ids, and empty input, and trims surrounding whitespace.