diff --git a/.gitignore b/.gitignore index 9aba0c8a7..42264de93 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ reports/* .python-version .cache coverage/ +.generated/ diff --git a/.sdd/manifest.json b/.sdd/manifest.json new file mode 100644 index 000000000..7fdf57f84 --- /dev/null +++ b/.sdd/manifest.json @@ -0,0 +1,665 @@ +{ + "manifest_version": 1, + "repository": { + "name": "react-widgets", + "purpose": "A single Git repository containing independently consumable React, Redux, and embeddable Webex widget packages plus their build and test tooling.", + "category": "cat1-legacy", + "primary_languages": ["javascript", "typescript", "scss"] + }, + "topology": "Single-repo", + "commands": { + "install": { + "command": "npm install --legacy-peer-deps", + "source_file": ".circleci/config.yml" + }, + "build": { + "command": "npm run build:all", + "source_file": "package.json" + }, + "build_widgets": { + "command": "npm run build:widgets", + "source_file": "package.json" + }, + "serve": { + "command": "npm start", + "source_file": "package.json" + }, + "lint": { + "command": "npm run static-analysis", + "source_file": "package.json" + }, + "test": { + "command": "npm run jest", + "source_file": "package.json" + }, + "test_smoke": { + "command": "npm run test:automation:smoke", + "source_file": "package.json" + }, + "test_space": { + "command": "npm run test:automation:space", + "source_file": "package.json" + }, + "test_recents": { + "command": "npm run test:automation:recents", + "source_file": "package.json" + }, + "publish": { + "command": "npm run publish:components", + "source_file": "package.json" + } + }, + "coverage_status_definitions": { + "specced": ">=80% public surface specced, drift <5%, independent validation complete, and last 5 PRs have zero unresolved MODIFIED requirements", + "partial": "40-80% documented, or documentation exceeds 80% but an independent validation/promotion gate is still pending", + "untracked": "<40% specced; code is the source of truth" + }, + "modules": [ + { + "path": "packages/node_modules/@webex/widget-space/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 94%: public exports, events, configuration, state, UI, error paths, and journey intent are covered; independent Cursor validation passed, while the last-five-PR drift gate still blocks promotion.", + "canonical_spec": "ai-docs/modules/space-messaging-spec.md", + "contracts": { + "provides": ["Space, Message, Files, and Roster widget package entrypoints", "current rooms/messages/calls/activity host events"], + "requires": ["widget runtime/auth", "Redux resource modules", "Webex SDK and Mercury", "shared UI and containers"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@webex/widget-space/README.md", "packages/node_modules/@webex/widget-space/events.md", "test/journeys/testplan.md", "packages/node_modules/@ciscospark/widget-*/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/widget-recents/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 95%: exports, filters, events, state transitions, host forms, and journey intent are covered; independent Cursor validation passed, while the last-five-PR drift gate still blocks promotion.", + "canonical_spec": "ai-docs/modules/recents-spec.md", + "contracts": { + "provides": ["Recents widget package entrypoint", "current room/message/call/membership/action host events"], + "requires": ["widget runtime/auth", "spaces/conversation/meeting state", "Webex SDK and Mercury"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@webex/widget-recents/README.md", "packages/node_modules/@webex/widget-recents/events.md", "test/journeys/testplan.md", "packages/node_modules/@ciscospark/widget-recents/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/widget-meet/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 92%: meeting creation, join/media/leave state, call UI, errors, and available tests are covered; independent Cursor validation passed, while sparse lifecycle tests and the last-five-PR drift gate block promotion.", + "canonical_spec": "ai-docs/modules/meetings-spec.md", + "contracts": { + "provides": ["Meet and Meetings widget package entrypoints", "meeting Redux actions/reducer"], + "requires": ["Webex meetings SDK collection", "media components", "widget runtime and Space integration"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@ciscospark/widget-meet/README.md", "test/journeys/testplan.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/widget-call-history/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 91%: all typed calling widgets, adapters, state/UI/error contracts, and compatibility hazards are inventoried; independent Cursor validation passed, while sparse direct tests and the last-five-PR drift gate block promotion.", + "canonical_spec": "ai-docs/modules/calling-spec.md", + "contracts": { + "provides": ["Call History, Speed Dial, Voice Mail, and Number Pad entrypoints"], + "requires": ["Webex component adapter interfaces/SDK adapter", "React Hook Form and shared calling UI"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@webex/widget-call-history/README.md", "packages/node_modules/@webex/widget-speed-dial/src/README.md", "packages/node_modules/@webex/widget-speed-dial/src/GenericModal/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/react-component-activity-item/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 93%: 54 public component entrypoints, key coupling, lifecycle, accessibility, host, and test rules are covered; independent Cursor validation passed, while export automation and promotion-history evidence remain.", + "canonical_spec": "ai-docs/modules/shared-ui-components-spec.md", + "contracts": { + "provides": ["public react-component package entrypoints", "shared component utility barrel"], + "requires": ["React/DOM", "Momentum UI", "CSS/SCSS pipeline", "selected Redux/SDK integrations"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": false, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": false, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@ciscospark/react-component-*/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/redux-module-activities/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 94%: all Redux package barrels, async/state conventions, meeting authority, errors, and tests are covered; independent Cursor validation passed, while late-result characterization and promotion-history evidence remain.", + "canonical_spec": "ai-docs/modules/state-management-spec.md", + "contracts": { + "provides": ["Redux reducers, actions, thunks, selectors, SDK/auth state"], + "requires": ["Redux/Immutable.js", "Webex SDK plugins and Mercury", "widget store composition"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": false, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": false, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@ciscospark/redux-module-*/README.md", "packages/node_modules/@ciscospark/react-redux-spark*/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/react-container-activity-list/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 92%: all container/HOC entrypoints, projections, state, scrolling, Mercury filters, errors, and lifecycle risks are covered; independent Cursor validation passed, while teardown characterization and promotion-history evidence remain.", + "canonical_spec": "ai-docs/modules/containers-hooks-spec.md", + "contracts": { + "provides": ["connected component package entrypoints", "conversation Mercury and scrollable HOCs"], + "requires": ["Redux slices/actions", "shared components", "Webex SDK/Mercury", "browser DOM"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["packages/node_modules/@ciscospark/react-container-*/README.md", "packages/node_modules/@ciscospark/react-hoc-*/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "packages/node_modules/@webex/webex-widget-base/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 93%: enhancer order, object-shaped constructor, host APIs, auth/current-user state, removal, demos, and README conflict are covered; independent Cursor validation passed, while direct lifecycle tests and promotion-history evidence still block promotion.", + "canonical_spec": "ai-docs/modules/widget-runtime-auth-spec.md", + "contracts": { + "provides": ["widget enhancer/runtime entrypoint", "browser/data APIs and registry", "sign-in and demo host surfaces"], + "requires": ["React/Redux", "Webex SDK/auth", "browser DOM and CustomEvent", "widget reducer maps and capability HOCs"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": true, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["README.md", "packages/node_modules/@webex/webex-widget-base/README.md", "packages/node_modules/@webex/widget-demo/README.md", "packages/node_modules/@ciscospark/spark-widget-base/README.md", "packages/node_modules/@ciscospark/widget-*-demo/README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + }, + { + "path": "scripts/build/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 95%: every npm/build/publish target, package filter, artifact path, CI promotion gate, SRI rule, and known legacy-script hazard is covered; independent Cursor validation passed, while publish-recovery and promotion-history evidence remain.", + "canonical_spec": "ai-docs/modules/build-release-tooling-spec.md", + "contracts": { + "provides": ["repository build/start/test/publish/deploy commands", "package and CDN artifacts"], + "requires": ["Node/npm lockfile", "Babel/Webpack/Rollup", "CI registry/CDN/Git credentials"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": false, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": false, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["README.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved; root README build/release units are accounted for by the repository-scope source-fidelity inventory and routed into this module" + } + }, + { + "path": "test/journeys/", + "coverage_status": "Partial", + "coverage_evidence": "Documentation assessment 96%: Jest and WDIO topology, every protected test-plan suite/scenario family, environment, helpers, async behavior, CI use, and known gaps are covered; independent Cursor validation passed, while calling-journey gaps and promotion-history evidence remain.", + "canonical_spec": "ai-docs/modules/test-automation-spec.md", + "contracts": { + "provides": ["Jest unit suites", "WDIO smoke/Space/Recents/TAP journeys", "JUnit/browser diagnostics"], + "requires": ["built widget assets", "local Selenium or Sauce Labs", "Webex test users and integration services"] + }, + "last_assessed": "2026-07-22", + "section_profile": { + "has_ui": false, + "crosses_service_boundaries": true, + "enforces_domain_rules": true, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": true, + "stateful_transitions": false, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": true, + "published_package": false, + "embedded_in_host": false, + "holds_client_state": false, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + }, + "source_policy": { + "mode": "reconcile", + "existing_sources": ["test/journeys/testplan.md"], + "conflict_status": "resolved", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + } + } + ], + "spec_source_policy": { + "mode": "reconcile", + "decided_by": "repository owner", + "decided_at": "2026-07-22T00:00:00+05:30", + "decision_record": "reconcile selected by repository owner on 2026-07-22; protected sources preserved and code-right conflicts resolved" + }, + "spec_sources": [ + { + "path": "README.md", + "scope": "repo", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/GETTING_STARTED.md", + "notes": "Preserved unchanged; supported setup and host API guidance was reconciled, while the limited widget list is retained as historical source wording." + }, + { + "path": "packages/node_modules/@ciscospark/*/README.md", + "scope": "reference", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/CONTRACTS.md", + "notes": "Ninety-three protected same-suffix namespace migration notices are preserved and represented by package-family Export Stability sections." + }, + { + "path": "packages/node_modules/@webex/webex-widget-base/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/webex-widget-base/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/widget-runtime-auth-spec.md", + "notes": "Preserved unchanged; current code contradicts its default two-argument construction example, so the object-shaped named constructor is canonical." + }, + { + "path": "packages/node_modules/@webex/widget-space/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-space/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/space-messaging-spec.md", + "notes": "Preserved unchanged; configuration and UI intent were checked against current props/constants." + }, + { + "path": "packages/node_modules/@webex/widget-space/events.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-space/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/space-messaging-spec.md", + "notes": "Preserved unchanged; only names present in current event constants are canonical, and obsolete notification/mention examples are flagged." + }, + { + "path": "packages/node_modules/@webex/widget-recents/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-recents/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/recents-spec.md", + "notes": "Preserved unchanged and reconciled with current configuration, filters, state, and host forms." + }, + { + "path": "packages/node_modules/@webex/widget-recents/events.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-recents/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/recents-spec.md", + "notes": "Preserved unchanged; current source event constants are canonical and obsolete notification examples are flagged." + }, + { + "path": "packages/node_modules/@webex/widget-call-history/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-call-history/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/calling-spec.md", + "notes": "Preserved unchanged and reconciled with the TypeScript entrypoint and adapter contracts." + }, + { + "path": "packages/node_modules/@webex/widget-speed-dial/src/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-speed-dial/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/calling-spec.md", + "notes": "Preserved unchanged and reconciled with current speed-dial implementation." + }, + { + "path": "packages/node_modules/@webex/widget-speed-dial/src/GenericModal/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-speed-dial/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/calling-spec.md", + "notes": "Preserved unchanged and reconciled with current modal tests/types." + }, + { + "path": "packages/node_modules/@webex/widget-demo/README.md", + "scope": "module", + "module": "packages/node_modules/@webex/widget-demo/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/widget-runtime-auth-spec.md", + "notes": "Preserved unchanged as development/demo guidance." + }, + { + "path": "test/journeys/testplan.md", + "scope": "module", + "module": "test/journeys/", + "role": "source-material", + "canonical": false, + "use_by_agents": "source-for-reconciliation", + "canonical_target": "ai-docs/modules/test-automation-spec.md", + "notes": "Preserved unchanged; every suite and scenario family is represented, while executable tests/config decide current proof." + } + ], + "spec_policy": { + "delta_grammar": { + "added": "## ADDED Requirements", + "modified": "## MODIFIED Requirements", + "removed": "## REMOVED Requirements" + }, + "require_what_and_why": true, + "require_provenance": true, + "protected_specs": [ + "README.md", + "packages/node_modules/@ciscospark/*/README.md", + "packages/node_modules/@webex/webex-widget-base/README.md", + "packages/node_modules/@webex/widget-call-history/README.md", + "packages/node_modules/@webex/widget-demo/README.md", + "packages/node_modules/@webex/widget-recents/README.md", + "packages/node_modules/@webex/widget-recents/events.md", + "packages/node_modules/@webex/widget-space/README.md", + "packages/node_modules/@webex/widget-space/events.md", + "packages/node_modules/@webex/widget-speed-dial/src/README.md", + "packages/node_modules/@webex/widget-speed-dial/src/GenericModal/README.md", + "test/journeys/testplan.md", + "AGENTS.md", + "ai-docs/ARCHITECTURE.md", + "ai-docs/CONTRACTS.md", + "ai-docs/GETTING_STARTED.md", + "ai-docs/GLOSSARY.md", + "ai-docs/REVIEW_CHECKLIST.md", + "ai-docs/RULES.md", + "ai-docs/SECURITY.md", + "ai-docs/SERVICE_STATE.md", + "ai-docs/SPEC_INDEX.md", + "ai-docs/modules/*.md" + ], + "required_sections_by_change_class": { + "core_always": ["Intent (WHAT)", "Rationale (WHY)", "Scope/Out-of-scope", "Acceptance criteria", "Contracts delta"], + "security_or_contract_or_perf_critical": ["Data", "Error Matrix", "Resilience", "Observability", "Operations"] + } + }, + "validation": { + "generator_runtime": "codex-desktop", + "generator_runtime_source": "host-metadata", + "validator_runtime": "cursor-agent", + "validator_run_id": "ccfd0bb3-f62d-4c12-8782-7ee3cdf85e63", + "validator_runtime_source": "host-metadata", + "runtime_fallback_tier": "different-runtime", + "minimum_independence": "different-runtime", + "blocking_severities": ["Blocking"], + "source_commit": "145a23ef33af4e74d8790832062d9910cd66b3de", + "base_ref": "master", + "head_ref": "react-widgets-migrate-into-sdd-bootstrap" + }, + "layout": { + "sdd_root": ".sdd", + "docs_root": "ai-docs", + "agent_entry_path": "AGENTS.md", + "standing_docs_root": "ai-docs", + "spec_index_path": "ai-docs/SPEC_INDEX.md", + "module_docs_strategy": "central", + "module_docs_root": "ai-docs/modules", + "module_docs_folder_name": "ai-docs", + "template_roots": { + "canonical": ".sdd/templates", + "extensions": [".sdd/templates/extensions"] + }, + "repo_skills_root": ".agents/skills", + "runtime_skill_entrypoints": { + "codex": ".agents/skills", + "cursor": ".cursor/skills" + }, + "contracts_strategy": "root-index-module-detail" + }, + "tooling": { + "sdlc_skills": { + "source_repo": "git@sqbu-github.cisco.com:WebexDevPlatform/SDLC-Skills.git", + "source_ref": "d5ec17e136514addc76df19760f1f00b05ba9c70", + "source_branch": "main", + "install_mode": "copy", + "installed_at": "2026-07-22T00:00:00+05:30", + "plugins": ["sdd-bootstrap@0.4.3", "template-library@0.2.1"] + } + }, + "coverage_ratchet": { + "enabled": true, + "waivers": [] + }, + "substrate": { + "source": "existing react-widgets repository conventions and CI configuration", + "consumed": ["ESLint/Babel/build configs", "README and contribution conventions", "CircleCI quality and release workflow"], + "compliance_tier": "legacy" + }, + "section_profiles": { + "repo": { + "owns_datastore": false, + "holds_client_state": true, + "components_interact": true, + "domain_data_across_components": true, + "caches_data": true, + "observability_convention": true, + "deploys_to_infra": true, + "shared_base_libs": true, + "is_monorepo": true, + "multi_platform": false, + "published_package": true, + "embedded_in_host": true, + "cross_repo_deps_material": true, + "security_arch_warranted": true + }, + "resolved_by": "codex-desktop with repository-owner approved plan and code-grounded questionnaire", + "resolved_at": "2026-07-22T00:00:00+05:30" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1888701f8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,113 @@ +# AGENTS.md — react-widgets + +> Read first. Next: [`ai-docs/SPEC_INDEX.md`](ai-docs/SPEC_INDEX.md) for routing and [`ai-docs/ARCHITECTURE.md`](ai-docs/ARCHITECTURE.md) for system shape. Load only the module specs needed for the task. + +## Repo Overview + +**react-widgets** is a browser-focused React package repository that provides Webex widgets, reusable UI components, Redux modules, host-integration helpers, build/release tooling, and browser journey tests. + +**What it is:** + +- A single root build that produces many `@webex/*` npm packages and selected CDN widget bundles. +- A client library that consumes Webex JavaScript SDK services and exposes React, browser-global, data-attribute, and event surfaces. + +**What it is NOT:** + +- It is not the Webex backend or the Webex JavaScript SDK implementation. +- It does not own a server datastore or API schema. +- It is not a native mobile or desktop application. + +## Tech Stack + +- JavaScript and TypeScript; React 16, Redux 3, Immutable.js, React-Redux, and recompose. +- Babel 7, Rollup 2, Webpack 4, npm, Jest 24, WebdriverIO 7, Sauce Labs, and axe-core. +- CI currently runs Node 22.22; `.nvmrc` selects the Node 22 LTS line (`lts/jod`). + +## Architecture + +```text +host application / HTML data attributes + ↓ +webex-widget-base (mount, auth/SDK, Redux provider, events, teardown) + ↓ +capability widgets → containers/HOCs → Redux modules → Webex JS SDK/services + ↓ ↘ shared React components +host callbacks + DOM/ampersand events +``` + +Full component responsibilities and interaction diagrams: [`ai-docs/ARCHITECTURE.md`](ai-docs/ARCHITECTURE.md). + +## Module / Package Structure + +```text +packages/node_modules/@webex/ +├── widget-* # capability widgets and demos +├── react-component-* # reusable UI components +├── react-container-* # state/SDK-connected components +├── react-hoc-* # reusable higher-order behavior +├── redux-module-* # actions, reducers, selectors, async thunks +├── react-redux-* # SDK/store integration, fixtures, metrics +└── webex-widget-base/ # common host/runtime composition +scripts/ # build, start, publish, deploy, and tooling commands +test/journeys/ # browser integration and accessibility suites +``` + +Module routing: [`ai-docs/SPEC_INDEX.md`](ai-docs/SPEC_INDEX.md). + +## Critical Rules + +1. **Code and tests describe current behavior.** Never invent a package export, prop, event, command, destination type, or SDK capability. +2. **Plan and confirm behavior changes.** This bootstrap is docs-only; future public API, event, auth, build, release, or test-policy changes require an approved spec delta. +3. **Preserve package entrypoints.** `src/index.js` / `src/index.ts`, package `main`/`module`, widget browser names, data-toggle names, and event strings are compatibility surfaces. +4. **Preserve enhancer ordering.** Data API and browser globals wrap Redux setup, widget removal, SDK injection, current-user loading, display name, and version metadata in `webex-widget-base`. +5. **Keep Redux state immutable.** Follow existing action/reducer/thunk patterns and never mutate stored records in place. +6. **Treat tokens and SDK instances as host-supplied credentials.** Never commit or log access tokens, guest tokens, client secrets, Sauce keys, npm tokens, Netlify tokens, or AWS credentials. +7. **Keep accessibility behavior testable.** Maintain roles, labels, keyboard navigation, focus transitions, and axe journey coverage when changing UI. +8. **Update SDD docs with behavior.** Change the owning module spec, contracts/state indexes, and manifest in the same change. + +## Essential Commands + +| Task | Command | +|---|---| +| Install | `npm install --legacy-peer-deps` | +| Build all packages | `npm run build:all` | +| Run local demo | `npm start` | +| Unit tests | `npm run jest` | +| Static analysis | `npm run static-analysis` | +| Full local verification | `npm test` | + +## Common Gotchas + +1. Package source is intentionally tracked under `packages/node_modules/@webex/`; do not treat it as installed third-party output. +2. Jest remaps `@webex/*` imports to package `src/` entrypoints, so tests exercise source rather than built `cjs/` or `es/` output. +3. Space and Recents use encrypted Mercury flows by default; Recents `basicMode` uses Webex REST and changes encryption behavior. +4. Widget teardown must call the browser widget's `remove()` path so React unmounts and `window.webex.widgetStore` is cleared. +5. The protected legacy docs include `@ciscospark/*` rename notices; keep them, but use current `@webex/*` entrypoints for new work. +6. Browser journey suites need credentials, built static assets, media permissions, and sometimes Sauce Connect; Jest does not cover those integration requirements. + +## Pre-Commit Checklist + +- [ ] `npm run static-analysis` passes. +- [ ] `npm run jest` passes. +- [ ] Positive and negative behavior tests cover changed logic; relevant journey/accessibility tests are identified. +- [ ] Public exports, props, events, browser/data APIs, and package compatibility remain intentional. +- [ ] Spec/docs and `.sdd/manifest.json` are current; local SDD run output remains ignored and unstaged. +- [ ] No credentials, generated bundles, `es/`, `cjs/`, `dist/`, reports, or coverage output are committed unintentionally. + +## External Source Access + +| Provider class | Source / host pattern | Preferred access | If unavailable | +|---|---|---|---| +| source host | `github.com/webex/*`, Cisco enterprise GitHub | authenticated CLI/connector or local clone | Use local code/history; do not guess missing PR rationale. | +| Webex APIs/SDK docs | `developer.webex.com`, `github.com/webex/webex-js-sdk` | official docs or installed dependency source | Mark contract detail unverified until access is available. | +| CI/browser grid | CircleCI, Sauce Labs | CI artifacts and authenticated service | Run local Jest/static analysis and record the journey gap. | + +## Strict Compliance Mode (automation) + +For SDD bootstrap, validation, CI, and release work, load the affected specs up front. A Blocking source-fidelity, conformance, code/spec, security, or public-contract finding halts progression until resolved. + +--- + +Per-module coverage state lives in `.sdd/manifest.json` and is mirrored in `ai-docs/SPEC_INDEX.md`. Independent Cursor validation passed on 2026-07-23; the bootstrap specs remain `Partial` until their documented weak-evidence gaps and the promotion-history gate are satisfied. + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-23`. diff --git a/ai-docs/ARCHITECTURE.md b/ai-docs/ARCHITECTURE.md new file mode 100644 index 000000000..3fa12d083 --- /dev/null +++ b/ai-docs/ARCHITECTURE.md @@ -0,0 +1,212 @@ +# ARCHITECTURE — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), then route through [`SPEC_INDEX.md`](SPEC_INDEX.md). Module detail lives under `ai-docs/modules/`. + +## Design Overview + +The repository is one build and release unit containing a package forest. Small presentational packages remain independently importable, while widgets compose them with connected containers, Redux modules, Webex SDK adapters, and a common browser runtime. This separation lets consumers use either a complete widget or lower-level pieces without duplicating Webex-specific state and integration behavior. + +`webex-widget-base` is the host boundary for legacy JavaScript widgets. Its enhancer chain registers the data API and browser globals, creates a Redux provider, injects Webex SDK state/authentication, loads the current user, exposes version metadata, and supports teardown. Newer TypeScript calling widgets instead receive component adapters through React contexts and expose typed React entrypoints. Evidence: `packages/node_modules/@webex/webex-widget-base/src/index.js`, `packages/node_modules/@webex/widget-call-history/src/contexts/AdapterContext.tsx`. + +## Component Inventory & Responsibilities + +| Component | Responsibility | Docs | +|---|---|---| +| Space and messaging packages | Compose space activities, messaging, files, roster, destinations, and host events. | `modules/space-messaging-spec.md` | +| Recents package | Load/filter spaces, react to realtime activities and membership changes, and emit host selection/call/profile events. | `modules/recents-spec.md` | +| Meetings packages | Resolve destinations and manage create/join/media/leave state and UI. | `modules/meetings-spec.md` | +| Calling packages | Render call history, dial pad, speed dials, voicemail, and adapter-driven call actions. | `modules/calling-spec.md` | +| Shared UI components | Provide presentational building blocks and UI utilities. | `modules/shared-ui-components-spec.md` | +| Redux/state packages | Own immutable client state, SDK thunks, event reducers, selectors, and metrics queues. | `modules/state-management-spec.md` | +| Containers/HOCs | Bind UI to Redux, SDK operations, file retrieval, notifications, presence, scrolling, and Mercury. | `modules/containers-hooks-spec.md` | +| Widget runtime/auth/demos | Mount widgets, establish auth/SDK context, expose browser/data APIs, and provide demos/samples. | `modules/widget-runtime-auth-spec.md` | +| Build/release tooling | Discover packages and build, transpile, sign, publish, deploy, and serve artifacts. | `modules/build-release-tooling-spec.md` | +| Test automation | Run Jest and browser journeys, accessibility checks, test-user setup, and CI reporting. | `modules/test-automation-spec.md` | + +## Component Interaction + +```mermaid +flowchart LR + Host[Host application or HTML] --> Runtime[Widget runtime and auth] + Runtime --> Widgets[Space, Recents, Meetings, Calling widgets] + Widgets --> Containers[Containers and HOCs] + Widgets --> UI[Shared UI components] + Containers --> State[Redux and state packages] + State --> SDK[Webex JavaScript SDK and component adapters] + SDK --> Services[Webex API and realtime services] + Widgets --> Events[Host callbacks, DOM events, ampersand events] + Tooling[Build and release tooling] --> Packages[ES, CommonJS, and CDN artifacts] + Tests[Test automation] --> Host + Tests --> Widgets +``` + +Hosts enter through imported React exports, `window.webex.widget(element)`, or `[data-toggle^="webex-"]`. Legacy widgets compose shared enhancers and reducers; Redux thunks call SDK plugins, and realtime listeners feed normalized state and host events. Calling widgets use typed adapter contexts rather than the legacy shared Redux runtime. + +## Widget Initialization & Interaction Flow + +```mermaid +sequenceDiagram + participant H as Host + participant B as webex-widget-base + participant R as Redux store + participant S as Webex SDK + participant W as Capability widget + H->>B: import/render or browser/data API options + B->>R: create reducers + thunk middleware + B->>S: inject supplied SDK or authenticate supplied token + S-->>R: instance and registration state + R-->>W: selectors inject current state + W->>S: thunk or adapter operation + S-->>W: response or realtime event + W-->>H: callback and/or DOM event + alt host removes widget + H->>B: remove() + B->>H: unmount React and remove widget-store entry + Note over B,R: browser remove() does not dispatch REMOVE_WIDGET + end +``` + +Evidence: `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js`, `packages/node_modules/@webex/widget-recents/src/enhancers/listeners.js`. + +## Dependencies + +| Dependency | Type | How used | Failure / version handling | +|---|---|---|---| +| React / ReactDOM | peer/runtime | Render components and mount browser widgets. | Root dependency range is `^16.8.4`; public component behavior is semver-sensitive. | +| Redux / React-Redux / Immutable.js | internal runtime | Widget stores, actions, reducers, selectors, and connected containers. | Reducers retain initial state on unknown actions; failed SDK operations enter error state or reject thunks. | +| Webex JavaScript SDK plugins | external packages/services | Authentication, devices, conversations, rooms, people, meetings, Mercury, search, teams, flags, and metrics. | Versions are pinned to the compatible `^2.60.4` family; callers surface errors/loading state. | +| Momentum UI and Webex Components | external package | Visual primitives, collaboration controls, and calling adapters. | Root versions are pinned; CSS and host-theme compatibility must be checked on upgrades. | +| Browser APIs | host platform | DOM mount, CustomEvent, media streams, notifications, localStorage, and audio/video. | Browser support and test configuration are explicit; unavailable permissions surface errors or disable behavior. | +| Babel/Rollup/Webpack | build | Transpile packages and bundle widgets. | Build fails on compile/config errors; generated `es/`, `cjs/`, and `dist/` are not source. | + +### State Model + +- Each legacy widget creates a combined Redux store with widget reducers plus `spark` and `users`. The reducer resets if `REMOVE_WIDGET` is dispatched, but the browser `remove()` implementation does not dispatch it. Evidence: `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`. +- State packages use Immutable.js maps/records for activities, conversations, spaces, users, media, meetings, errors, features, flags, presence, search, teams, and metrics. Evidence: `packages/node_modules/@webex/redux-module-meetings/src/reducer.js`. +- Setup enhancers advance from SDK authentication/registration to Mercury connection, initial fetch, avatar/team/feature loading, ready/error display, and teardown. Evidence: `packages/node_modules/@webex/widget-recents/src/enhancers/setup.js`. + +## Cross-Cutting Concerns + +- **Security:** credentials enter as host props or environment variables; SDK/API calls cross the trust boundary. Never persist or log tokens. Browser globals and data attributes are public inputs and must be validated against component prop contracts. +- **Observability:** development Redux logging, SDK logger calls, CI JUnit/browser artifacts, and the metrics HOC expose state/action timing and failures. Call payloads are deliberately omitted from Recents event logs to avoid range/serialization problems. + +## Performance, Compatibility & Accessibility + +The repository targets the browser matrix in `babel.config.js`, including legacy ES5 output. Rollup externalizes core peer UI libraries and hashes CSS-module class names; Webpack produces self-contained widget bundles. Recents loads a bounded initial set (default 25), then avatars/teams/features, and accessibility journeys run axe checks. Public package exports, widget names, data attributes, event strings, keyboard behavior, and SRI manifests are compatibility surfaces. + +## Dependency / Interaction Topology + +```mermaid +flowchart TB + Widgets --> Base[webex-widget-base] + Widgets --> Containers + Widgets --> Components + Containers --> Redux[redux-module-*] + Base --> Spark[react-redux-spark] + Redux --> SDK[Webex SDK plugins] + Spark --> SDK + SDK -. realtime .-> Mercury[Mercury] + Mercury -. activities .-> Redux + Widgets -. events .-> Host +``` + +| From | To | Kind | Purpose | +|---|---|---|---| +| Widget entrypoints | `webex-widget-base` | in-process composition | Install common host, Redux, auth, version, and teardown behavior. | +| Containers/HOCs | Redux modules | in-process calls | Dispatch state transitions and select display props. | +| Redux thunks/runtime | Webex SDK | promise/event calls | Fetch or mutate Webex resources and connect realtime services. | +| Widgets | Host | callbacks/DOM events | Report message, room, call, membership, profile, and activity behavior. | + +## Object / Data Ownership + +| Domain object | Client-state owner | Read by | +|---|---|---| +| SDK authentication/registration state | `react-redux-spark` | all legacy widgets and setup enhancers | +| Space/conversation/activity/user/team state | matching `redux-module-*` packages | Space, Recents, Message, Roster, and connected components | +| Call/media state | `redux-module-media` and widget reducers | Meet/Space/Recents call UI | +| Meeting IDs and media readiness | `redux-module-meetings` | Meetings widget selectors/components | +| Widget-local status/config | each widget reducer or React hook state | owning widget only | + +Remote Webex services remain systems of record; this repository owns only client representations and transitions. + +## Caching Catalog + +| Cache | Backend | What it holds | Lifetime | Invalidation trigger | +|---|---|---|---|---| +| widget store | `window.webex.widgetStore` | mounted `BrowserWidget` objects by UUID | widget lifetime | `remove()` deletes the UUID entry | +| Redux resource maps | in-memory Immutable.js store | spaces, users, activities, files, meetings, and related status | widget/Provider lifetime | actions replace/remove records; an explicit `REMOVE_WIDGET` action resets state, but browser `remove()` does not dispatch it | +| downloaded share reuse | Redux share state | decrypted/downloadable file blob | widget/Provider lifetime | new fetch/reducer update; browser removal relies on React unmount and reference release | +| number-pad focus flags | browser `localStorage` | focus handoff flags | interaction-scoped | blur/unmount handlers remove keys | + +## Observability Patterns + +- **Logging:** SDK logger calls record setup, events, warnings, and adaptive-card failures; development stores add `redux-logger`. Do not log tokens or raw call objects. +- **Metrics:** `react-redux-spark-metrics` queues named start/end metrics and sends them through the SDK metrics plugin when available. +- **Audit:** CI stores JUnit and browser artifacts; no product audit trail is owned by this client library. + +## Infrastructure Matrix + +| Category | In use | Notes | +|---|---|---| +| Datastores | none owned | Browser/Redux state only; Webex services own remote data. | +| Messaging / streaming | Webex Mercury | SDK-managed realtime conversation/activity channel. | +| Cloud / platform services | CircleCI, Sauce Labs, AWS S3/CloudFront, npm registry, Netlify tooling | Build, browser test, publish, and CDN deployment integrations. | + +## Shared / Base Libraries + +| Library | Shared responsibility | Version floor/source | +|---|---|---| +| `@webex/webex-widget-base` | Legacy widget mount, Redux, SDK/auth, current user, browser/data APIs, teardown, intl composition. | `packages/node_modules/@webex/webex-widget-base/src/index.js` | +| `@webex/react-component-utils` | Hydra IDs, activity/file/card/string/mention helpers and constants. | `packages/node_modules/@webex/react-component-utils/src/index.js` | +| `@webex/react-redux-spark` | SDK instance/authentication/device state injection. | `packages/node_modules/@webex/react-redux-spark/src/index.js` | +| Momentum UI / Webex Components | Shared visual controls and adapter interfaces. | pinned in `package.json` | + +## Package Map & Inter-Package Dependencies + +- Workspace convention: tracked packages live under `packages/node_modules/@webex/*`; a root discovery utility enumerates package paths rather than npm workspaces. +- Visibility: packages with `private: true` are build/demo/internal-only; other package entrypoints are publishable npm surfaces. +- Dependency direction: widgets depend on base/runtime, containers, Redux modules, and components; containers depend on Redux/SDK helpers; components should remain reusable and primarily presentational. +- Release rule: root versioning and publish tooling build non-private packages together; breaking exported changes require a compatible release and migration note. + +## Release & Versioning + +- `standard-version` generates root versions/changelog entries using Conventional Commits. Non-private packages are built and published publicly through root tooling. +- CI builds Space, Recents, and Demo CDN archives, creates SRI manifests/signatures, syncs alpha/latest/archive prefixes to S3, and invalidates CloudFront. +- Consumers can read bundle version metadata from headers, `window.webex.widgetFn.{name}.version`, or a mounted widget object. + +## Host Integration & Theming + +- Imported React components use package entrypoints and package CSS/Sass as documented by the owning module. +- Browser-global hosts call `window.webex.widget(element).{name}Widget(options)`; data-API hosts use `data-toggle="webex-{name}"` plus data attributes. +- Hosts provide tokens or a pre-authenticated SDK instance and must load compatible Webex SDK/plugin versions. The legacy alias `window.ciscospark` points at `window.webex`. +- Momentum UI/Webex Components styles and generated CSS-module class names are host-facing visual dependencies. + +## Cross-Repo Dependency Graph + +- **Internal packages consumed:** Webex JS SDK plugins, Webex Components, adapter interfaces, Webex style guide, and test-helper packages. +- **Consumers:** npm applications and sites embedding CDN bundles or importing individual `@webex/*` packages. +- **External services:** Webex identity/API/realtime services, npm, Sauce Labs, AWS S3/CloudFront, CircleCI, and optional Netlify deployment. + +## Security Architecture + +The browser host is outside the library trust boundary and supplies credentials, DOM elements, data attributes, callback functions, destination IDs, and optional SDK adapters. `react-redux-spark` establishes authenticated SDK state, after which setup enhancers register devices/connect Mercury and dispatch SDK operations. Network transport and remote authorization are owned by the Webex SDK/services; this repository must avoid broadening token exposure through logs, browser storage, examples, or generated artifacts. + +--- + +Per-module orientation and design: [`SPEC_INDEX.md`](SPEC_INDEX.md). + +## Architecture Reference Links + +| Reference | Location | When to read | +|---|---|---| +| Architecture decisions | `adr/` | Before changing module boundaries, central doc layout, or compatibility policy. | +| Repo patterns | `patterns/` | Before adding components, Redux behavior, or package exports. | +| Enforceable rules | `RULES.md` and `rules/` | Before any implementation or release change. | + +## WS6 References + +No WS6/platform architecture document is tracked in this repository. External architecture claims are unsupported until an authoritative source is supplied; current local implications derive only from `package.json`, source, tests, and CI configuration. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/CONTRACTS.md b/ai-docs/CONTRACTS.md new file mode 100644 index 000000000..ff76c9fca --- /dev/null +++ b/ai-docs/CONTRACTS.md @@ -0,0 +1,204 @@ +# Contracts Catalog — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), route through [`SPEC_INDEX.md`](SPEC_INDEX.md), and use this as the root public-surface index. Exact declarations live at package entrypoints; `.sdd/manifest.json` carries the machine baseline. + +> Read before adding or changing a package export, widget host API, event, or root command. This catalog summarizes entrypoints and links to source; it does not duplicate full prop/type declarations. + +### Exported API & Types + +| Contract ID | Owner module | Symbol / package surface | Signature | Stability / deprecation | Detail and definition | +|---|---|---|---|---|---| +| `rw.ui.activity-item` | shared-ui-components | `@webex/react-component-activity-item` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-item/src/index.js` | +| `rw.ui.activity-item-base` | shared-ui-components | `@webex/react-component-activity-item-base` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-item-base/src/index.js` | +| `rw.ui.activity-list` | shared-ui-components | `@webex/react-component-activity-list` | default component + item-type constants | public semver surface | `packages/node_modules/@webex/react-component-activity-list/src/index.js` | +| `rw.ui.activity-menu` | shared-ui-components | `@webex/react-component-activity-menu` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-menu/src/index.js` | +| `rw.ui.activity-menu-header` | shared-ui-components | `@webex/react-component-activity-menu-header` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-menu-header/src/index.js` | +| `rw.ui.activity-post` | shared-ui-components | `@webex/react-component-activity-post` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-post/src/index.js` | +| `rw.ui.activity-post-action` | shared-ui-components | `@webex/react-component-activity-post-action` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-post-action/src/index.js` | +| `rw.ui.activity-share-file` | shared-ui-components | `@webex/react-component-activity-share-file` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-share-file/src/index.js` | +| `rw.ui.activity-share-files` | shared-ui-components | `@webex/react-component-activity-share-files` | connected default + named component | public semver surface | `packages/node_modules/@webex/react-component-activity-share-files/src/index.js` | +| `rw.ui.activity-share-thumbnail` | shared-ui-components | `@webex/react-component-activity-share-thumbnail` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-share-thumbnail/src/index.js` | +| `rw.ui.activity-system-message` | shared-ui-components | `@webex/react-component-activity-system-message` | component + system-message verb constants | public semver surface | `packages/node_modules/@webex/react-component-activity-system-message/src/index.js` | +| `rw.ui.activity-text` | shared-ui-components | `@webex/react-component-activity-text` | default React component | public semver surface | `packages/node_modules/@webex/react-component-activity-text/src/index.js` | +| `rw.ui.adaptive-card` | shared-ui-components | `@webex/react-component-adaptive-card` | connected React component | public semver surface | `packages/node_modules/@webex/react-component-adaptive-card/src/index.js` | +| `rw.ui.add-file-button` | shared-ui-components | `@webex/react-component-add-file-button` | default React component | public semver surface | `packages/node_modules/@webex/react-component-add-file-button/src/index.js` | +| `rw.ui.audio` | shared-ui-components | `@webex/react-component-audio` | media-stream React component | public semver surface | `packages/node_modules/@webex/react-component-audio/src/index.js` | +| `rw.ui.avatar` | shared-ui-components | `@webex/react-component-avatar` | default React component | public semver surface | `packages/node_modules/@webex/react-component-avatar/src/index.js` | +| `rw.ui.badge` | shared-ui-components | `@webex/react-component-badge` | default React component | public semver surface | `packages/node_modules/@webex/react-component-badge/src/index.js` | +| `rw.ui.button` | shared-ui-components | `@webex/react-component-button` | default React component | public semver surface | `packages/node_modules/@webex/react-component-button/src/index.js` | +| `rw.ui.button-controls` | shared-ui-components | `@webex/react-component-button-controls` | default React component | public semver surface | `packages/node_modules/@webex/react-component-button-controls/src/index.js` | +| `rw.ui.call-data-activity` | shared-ui-components | `@webex/react-component-call-data-activity` | default call activity component | public semver surface | `packages/node_modules/@webex/react-component-call-data-activity/src/index.js` | +| `rw.ui.chip-base` | shared-ui-components | `@webex/react-component-chip-base` | default React component | public semver surface | `packages/node_modules/@webex/react-component-chip-base/src/index.js` | +| `rw.ui.chip-file` | shared-ui-components | `@webex/react-component-chip-file` | default React component | public semver surface | `packages/node_modules/@webex/react-component-chip-file/src/index.js` | +| `rw.ui.confirmation-modal` | shared-ui-components | `@webex/react-component-confirmation-modal` | default modal component | public semver surface | `packages/node_modules/@webex/react-component-confirmation-modal/src/index.js` | +| `rw.ui.cover` | shared-ui-components | `@webex/react-component-cover` | default React component | public semver surface | `packages/node_modules/@webex/react-component-cover/src/index.js` | +| `rw.ui.day-separator` | shared-ui-components | `@webex/react-component-day-separator` | default React component | public semver surface | `packages/node_modules/@webex/react-component-day-separator/src/index.js` | +| `rw.ui.error-display` | shared-ui-components | `@webex/react-component-error-display` | default error component | public semver surface | `packages/node_modules/@webex/react-component-error-display/src/index.js` | +| `rw.ui.file-share-display` | shared-ui-components | `@webex/react-component-file-share-display` | default React component | public semver surface | `packages/node_modules/@webex/react-component-file-share-display/src/index.js` | +| `rw.ui.file-staging-area` | shared-ui-components | `@webex/react-component-file-staging-area` | default React component | public semver surface | `packages/node_modules/@webex/react-component-file-staging-area/src/index.js` | +| `rw.ui.icon` | shared-ui-components | `@webex/react-component-icon` | default icon + `ICONS` catalog | public semver surface | `packages/node_modules/@webex/react-component-icon/src/index.js` | +| `rw.ui.incoming-call` | shared-ui-components | `@webex/react-component-incoming-call` | default call component | public semver surface | `packages/node_modules/@webex/react-component-incoming-call/src/index.js` | +| `rw.ui.join-call-button` | shared-ui-components | `@webex/react-component-join-call-button` | default React component | public semver surface | `packages/node_modules/@webex/react-component-join-call-button/src/index.js` | +| `rw.ui.list-separator` | shared-ui-components | `@webex/react-component-list-separator` | default React component | public semver surface | `packages/node_modules/@webex/react-component-list-separator/src/index.js` | +| `rw.ui.loading-screen` | shared-ui-components | `@webex/react-component-loading-screen` | default React component | public semver surface | `packages/node_modules/@webex/react-component-loading-screen/src/index.js` | +| `rw.ui.md-choiceset-input` | shared-ui-components | `@webex/react-component-md-choiceset-input` | adaptive-card input component | public semver surface | `packages/node_modules/@webex/react-component-md-choiceset-input/src/index.js` | +| `rw.ui.md-text-input` | shared-ui-components | `@webex/react-component-md-text-input` | adaptive-card input component | public semver surface | `packages/node_modules/@webex/react-component-md-text-input/src/index.js` | +| `rw.ui.md-toggle-input` | shared-ui-components | `@webex/react-component-md-toggle-input` | adaptive-card input component | public semver surface | `packages/node_modules/@webex/react-component-md-toggle-input/src/index.js` | +| `rw.ui.new-messages-separator` | shared-ui-components | `@webex/react-component-new-messages-separator` | default React component | public semver surface | `packages/node_modules/@webex/react-component-new-messages-separator/src/index.js` | +| `rw.ui.people-list` | shared-ui-components | `@webex/react-component-people-list` | default React component | public semver surface | `packages/node_modules/@webex/react-component-people-list/src/index.js` | +| `rw.ui.presence-avatar` | shared-ui-components | `@webex/react-component-presence-avatar` | default React component | public semver surface | `packages/node_modules/@webex/react-component-presence-avatar/src/index.js` | +| `rw.ui.ringtone` | shared-ui-components | `@webex/react-component-ringtone` | component + incoming/ringback constants | public semver surface | `packages/node_modules/@webex/react-component-ringtone/src/index.js` | +| `rw.ui.scroll-to-bottom` | shared-ui-components | `@webex/react-component-scroll-to-bottom-button` | default React component | public semver surface | `packages/node_modules/@webex/react-component-scroll-to-bottom-button/src/index.js` | +| `rw.ui.space-item` | shared-ui-components | `@webex/react-component-space-item` | default React component | public semver surface | `packages/node_modules/@webex/react-component-space-item/src/index.js` | +| `rw.ui.spaces-list` | shared-ui-components | `@webex/react-component-spaces-list` | default React component | public semver surface | `packages/node_modules/@webex/react-component-spaces-list/src/index.js` | +| `rw.ui.spark-fonts` | shared-ui-components | `@webex/react-component-spark-fonts` | font stylesheet side-effect entry | public semver surface | `packages/node_modules/@webex/react-component-spark-fonts/src/index.js` | +| `rw.ui.spark-logo` | shared-ui-components | `@webex/react-component-spark-logo` | default logo component | public semver surface | `packages/node_modules/@webex/react-component-spark-logo/src/index.js` | +| `rw.ui.spark-oauth` | shared-ui-components | `@webex/react-component-spark-oauth` | default OAuth UI component | public semver surface; legacy name | `packages/node_modules/@webex/react-component-spark-oauth/src/index.js` | +| `rw.ui.spinner` | shared-ui-components | `@webex/react-component-spinner` | default React component | public semver surface | `packages/node_modules/@webex/react-component-spinner/src/index.js` | +| `rw.ui.textarea` | shared-ui-components | `@webex/react-component-textarea` | default React component | public semver surface | `packages/node_modules/@webex/react-component-textarea/src/index.js` | +| `rw.ui.timer` | shared-ui-components | `@webex/react-component-timer` | default React component | public semver surface | `packages/node_modules/@webex/react-component-timer/src/index.js` | +| `rw.ui.title-bar` | shared-ui-components | `@webex/react-component-title-bar` | default React component | public semver surface | `packages/node_modules/@webex/react-component-title-bar/src/index.js` | +| `rw.ui.typing-avatar` | shared-ui-components | `@webex/react-component-typing-avatar` | default React component | public semver surface | `packages/node_modules/@webex/react-component-typing-avatar/src/index.js` | +| `rw.ui.typing-indicator` | shared-ui-components | `@webex/react-component-typing-indicator` | default React component | public semver surface | `packages/node_modules/@webex/react-component-typing-indicator/src/index.js` | +| `rw.ui.utils` | shared-ui-components | `@webex/react-component-utils` | named utility/constants barrel | public semver surface | `packages/node_modules/@webex/react-component-utils/src/index.js` | +| `rw.ui.video` | shared-ui-components | `@webex/react-component-video` | media-stream React component | public semver surface | `packages/node_modules/@webex/react-component-video/src/index.js` | +| `rw.container.activity-list` | containers-hooks | `@webex/react-container-activity-list` | connected default component | public semver surface | `packages/node_modules/@webex/react-container-activity-list/src/index.js` | +| `rw.container.file-downloader` | containers-hooks | `@webex/react-container-file-downloader` | default injecting HOC | public semver surface | `packages/node_modules/@webex/react-container-file-downloader/src/index.js` | +| `rw.container.message-composer` | containers-hooks | `@webex/react-container-message-composer` | component/actions/reducer barrel | public semver surface | `packages/node_modules/@webex/react-container-message-composer/src/index.js` | +| `rw.container.notifications` | containers-hooks | `@webex/react-container-notifications` | component/actions/reducer barrel | public semver surface | `packages/node_modules/@webex/react-container-notifications/src/index.js` | +| `rw.container.presence-avatar` | containers-hooks | `@webex/react-container-presence-avatar` | connected component + reducers | public semver surface | `packages/node_modules/@webex/react-container-presence-avatar/src/index.js` | +| `rw.container.read-receipts` | containers-hooks | `@webex/react-container-read-receipts` | named component + connected default | public semver surface | `packages/node_modules/@webex/react-container-read-receipts/src/index.js` | +| `rw.container.scrolling-activity` | containers-hooks | `@webex/react-container-scrolling-activity` | named component + wrapped default | public semver surface | `packages/node_modules/@webex/react-container-scrolling-activity/src/index.js` | +| `rw.hoc.conversation-mercury` | containers-hooks | `@webex/react-hoc-conversation-mercury` | default HOC factory | public semver surface | `packages/node_modules/@webex/react-hoc-conversation-mercury/src/index.js` | +| `rw.hoc.scrollable` | containers-hooks | `@webex/react-hoc-scrollable` | default HOC factory | public semver surface | `packages/node_modules/@webex/react-hoc-scrollable/src/index.js` | +| `rw.state.spark` | state-management | `@webex/react-redux-spark` | reducer/initial state + SDK injection HOC | public semver surface | `packages/node_modules/@webex/react-redux-spark/src/index.js` | +| `rw.state.spark-fixtures` | test-automation | `@webex/react-redux-spark-fixtures` | mock store/SDK fixtures | public test-helper surface | `packages/node_modules/@webex/react-redux-spark-fixtures/src/index.js` | +| `rw.state.spark-metrics` | state-management | `@webex/react-redux-spark-metrics` | reducer/events/metrics HOC | public semver surface | `packages/node_modules/@webex/react-redux-spark-metrics/src/index.js` | +| `rw.state.activities` | state-management | `@webex/redux-module-activities` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-activities/src/index.js` | +| `rw.state.activity` | state-management | `@webex/redux-module-activity` | actions, reducer, message helper | public semver surface | `packages/node_modules/@webex/redux-module-activity/src/index.js` | +| `rw.state.avatar` | state-management | `@webex/redux-module-avatar` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-avatar/src/index.js` | +| `rw.state.conversation` | state-management | `@webex/redux-module-conversation` | actions, utilities, reducer | public semver surface | `packages/node_modules/@webex/redux-module-conversation/src/index.js` | +| `rw.state.errors` | state-management | `@webex/redux-module-errors` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-errors/src/index.js` | +| `rw.state.features` | state-management | `@webex/redux-module-features` | reducer, constants, async feature getter | public semver surface | `packages/node_modules/@webex/redux-module-features/src/index.js` | +| `rw.state.flags` | state-management | `@webex/redux-module-flags` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-flags/src/index.js` | +| `rw.state.indicators` | state-management | `@webex/redux-module-indicators` | reducer, constants, typing actions | public semver surface | `packages/node_modules/@webex/redux-module-indicators/src/index.js` | +| `rw.state.media` | state-management | `@webex/redux-module-media` | actions, enhancer, reducer, call helpers | public semver surface | `packages/node_modules/@webex/redux-module-media/src/index.js` | +| `rw.state.meetings` | meetings | `@webex/redux-module-meetings` | meeting thunks, reducer, destination lookup | public semver surface | `packages/node_modules/@webex/redux-module-meetings/src/index.js` | +| `rw.state.mercury` | state-management | `@webex/redux-module-mercury` | reducer, enhancer, realtime actions | public semver surface | `packages/node_modules/@webex/redux-module-mercury/src/index.js` | +| `rw.state.presence` | state-management | `@webex/redux-module-presence` | reducer/actions + presence constants | public semver surface | `packages/node_modules/@webex/redux-module-presence/src/index.js` | +| `rw.state.search` | state-management | `@webex/redux-module-search` | reducer/constants + user search thunk | public semver surface | `packages/node_modules/@webex/redux-module-search/src/index.js` | +| `rw.state.share` | state-management | `@webex/redux-module-share` | reducer/constants + shared-file thunk | public semver surface | `packages/node_modules/@webex/redux-module-share/src/index.js` | +| `rw.state.spaces` | state-management | `@webex/redux-module-spaces` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-spaces/src/index.js` | +| `rw.state.teams` | state-management | `@webex/redux-module-teams` | actions + reducer/initial state | public semver surface | `packages/node_modules/@webex/redux-module-teams/src/index.js` | +| `rw.state.users` | state-management | `@webex/redux-module-users` | actions, reducer, records | public semver surface | `packages/node_modules/@webex/redux-module-users/src/index.js` | +| `rw.test.react-utils` | test-automation | `@webex/react-test-utils` | intl-aware render helpers | public test-helper surface | `packages/node_modules/@webex/react-test-utils/src/index.js` | +| `rw.runtime.sign-in` | widget-runtime-auth | `@webex/webex-sign-in-page` | named typed sign-in component | public semver surface | `packages/node_modules/@webex/webex-sign-in-page/src/index.ts` | +| `rw.runtime.base` | widget-runtime-auth | `@webex/webex-widget-base` | `constructWebexEnhancer`, default helper, selected HOCs | public semver surface; default-helper discrepancy documented | `packages/node_modules/@webex/webex-widget-base/src/index.js` | +| `rw.widget.call-history` | calling | `@webex/widget-call-history` | `CallHistoryItem`, `CallHistory`, `NoHistory`, item types | public semver surface | `packages/node_modules/@webex/widget-call-history/src/index.ts` | +| `rw.widget.files` | space-messaging | `@webex/widget-files` | default connected file widget | public semver surface | `packages/node_modules/@webex/widget-files/src/index.js` | +| `rw.widget.meet` | meetings | `@webex/widget-meet` | default widget + reducers + destination types | public semver surface | `packages/node_modules/@webex/widget-meet/src/index.js` | +| `rw.widget.meetings` | meetings | `@webex/widget-meetings` | default widget + reducers + destination types | public semver surface | `packages/node_modules/@webex/widget-meetings/src/index.js` | +| `rw.widget.message` | space-messaging | `@webex/widget-message` | default widget + reducers + destination types | public semver surface | `packages/node_modules/@webex/widget-message/src/index.js` | +| `rw.widget.number-pad` | calling | `@webex/widget-number-pad` | number pad, call buttons, contact/search/popover components | public semver surface | `packages/node_modules/@webex/widget-number-pad/src/index.ts` | +| `rw.widget.recents` | recents | `@webex/widget-recents` | default widget + reducers | public semver surface | `packages/node_modules/@webex/widget-recents/src/index.js` | +| `rw.widget.roster` | space-messaging | `@webex/widget-roster` | default connected roster widget + reducers | public semver surface | `packages/node_modules/@webex/widget-roster/src/index.js` | +| `rw.widget.space` | space-messaging | `@webex/widget-space` | default widget + events/reducers/destination types | public semver surface | `packages/node_modules/@webex/widget-space/src/index.js` | +| `rw.widget.speed-dial` | calling | `@webex/widget-speed-dial` | `SpeedDials`, `SpeedDialItem`, `SpeedDialForm`, `SpeedDialSearch`, `SpeedDialAddBanner`, and exported form/list types | public semver surface | `packages/node_modules/@webex/widget-speed-dial/src/index.ts` | +| `rw.widget.voice-mail` | calling | `@webex/widget-voice-mail` | voicemail item/playback/scrubbing components | public semver surface | `packages/node_modules/@webex/widget-voice-mail/src/index.ts` | + +Internal-only package entrypoints (`private-react-component-*` and `widget-*-demo`) are indexed in their module specs but are not public npm contracts. + +Capability-level aggregate IDs provide stable cross-references from module specs; the package/event rows above and below remain the exact symbol-level catalog. + +| Contract ID | Owner module | Aggregate surface | Compatibility | Definition / exact members | +|---|---|---|---|---| +| `rw.ui.components` | shared-ui-components | 54 public `@webex/react-component-*` entrypoints | each entrypoint is a public semver surface | individual exact paths in the `rw.ui.*` rows above; representative: `packages/node_modules/@webex/react-component-activity-item/src/index.js`, `packages/node_modules/@webex/react-component-video/src/index.js` | +| `rw.state.modules` | state-management | public `@webex/redux-module-*` barrels | action/reducer/selector exports are public semver surfaces | individual exact paths in the `rw.state.*` rows above; representative: `packages/node_modules/@webex/redux-module-activity/src/index.js`, `packages/node_modules/@webex/redux-module-spaces/src/index.js` | +| `rw.state.sdk` | state-management | `@webex/react-redux-spark*` barrels | runtime and metrics exports are public; fixtures are test-oriented | `packages/node_modules/@webex/react-redux-spark/src/index.js`, `packages/node_modules/@webex/react-redux-spark-metrics/src/index.js`, `packages/node_modules/@webex/react-redux-spark-fixtures/src/index.js` | +| `rw.containers` | containers-hooks | seven public `@webex/react-container-*` entrypoints | each entrypoint is a public semver surface | individual exact paths in the `rw.container.*` rows above; representative: `packages/node_modules/@webex/react-container-activity-list/src/index.js`, `packages/node_modules/@webex/react-container-message-composer/src/index.js` | +| `rw.space.destinations` | space-messaging | `email`, `userId`, `spaceId`, `sip`, `pstn` where accepted | exact destination strings are stable | `packages/node_modules/@webex/widget-space/src/constants.js`, `packages/node_modules/@webex/widget-space/src/index.js` | +| `rw.space.events` | space-messaging | emitted Space/child events plus exported event constants | only events with an observed emitter are active host contracts | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | +| `rw.recents.options` | recents | public PropTypes/defaults and data-API options | accepted option names are compatibility surfaces; `muteNotifications` is accepted but unused | `packages/node_modules/@webex/widget-recents/src/container.js` | +| `rw.recents.events` | recents | current Recents host event set | exact emitted strings are stable | `packages/node_modules/@webex/widget-recents/src/events.js`, `packages/node_modules/@webex/widget-recents/src/container.js`, `packages/node_modules/@webex/widget-recents/src/enhancers/listeners.js` | +| `rw.meet.events` | meetings | legacy Meet call event constants | `calls:created/connected/disconnected` are emitted; membership constants have no emitter in current source | `packages/node_modules/@webex/widget-meet/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | + +### Browser and Data APIs + +| Contract ID | Owner | Surface | Purpose | Compatibility | Defined at | +|---|---|---|---|---|---| +| `rw.host.widget.select` | widget-runtime-auth | `window.webex.widget(element)` | select or create a mounted browser widget object | stable; `window.ciscospark` compatibility alias remains | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | +| `rw.host.widget.mount` | widget-runtime-auth | `widget.{name}Widget(options)` | render a registered widget into the selected element | widget names/options are public | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | +| `rw.host.widget.remove` | widget-runtime-auth | `widget.remove(callback?) -> Promise` | unmount React and clear widget registry | callback and promise forms remain supported | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | +| `rw.host.data-api` | widget-runtime-auth | `[data-toggle^="webex-{name}"]` + `data-*` | auto-mount widgets at DOM ready | kebab-to-camel option mapping is public | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withDataAPI.js` | +| `rw.host.version` | build-release-tooling | `widgetFn.{name}.version` and widget object `.version` | expose bundle version to hosts | preserved with release metadata | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | + +### Events + +| Contract ID | Owner module | Event | Direction | Payload detail | Delivery guarantees | Compatibility | Defined at | +|---|---|---|---|---|---|---|---| +| `rw.event.messages-created` | Space, Recents | `messages:created` | publish | message/actor/room/person/file fields | synchronous callback + DOM/ampersand dispatch after internal handling | string stable; additive payload changes only | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.rooms-unread` | Space, Recents | `rooms:unread` | publish | room/space summary | emitted from realtime activity handling; not for current user's own message where implemented | string stable | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.rooms-read` | Space, Recents | `rooms:read` | publish | room/space summary | emitted after read-state transition | string stable | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.rooms-selected` | Recents | `rooms:selected` | publish | room summary; optional `action: call` | user-driven | string/action stable | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.calls-created` | Space, Recents | `calls:created` | publish | call plus available room ID | SDK/realtime driven | string stable | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | +| `rw.event.calls-connected` | Space | `calls:connected` | publish | call lifecycle data | SDK event driven | string stable | `packages/node_modules/@webex/widget-space/src/events.js` | +| `rw.event.calls-disconnected` | Space | `calls:disconnected` | publish | call lifecycle data | SDK event driven | string stable | `packages/node_modules/@webex/widget-space/src/events.js` | +| `rw.event.activity-changed` | Space | `activity:changed` | publish | selected activity | user/external-control driven | string stable | `packages/node_modules/@webex/widget-space/src/events.js` | +| `rw.event.memberships-created` | Recents | `memberships:created` | publish | membership/actor/room/person data | Mercury driven | string stable | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.memberships-deleted` | Recents | `memberships:deleted` | publish | membership/actor/room/person data | Mercury driven | string stable | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.add-clicked` | Recents | `add:clicked` | publish | empty object | user-driven; option-gated | string stable | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.profile-clicked` | Recents | `profile:clicked` | publish | current-user profile/avatar data | user-driven; option-gated | string stable | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rw.event.signout-clicked` | Recents | `user_signout:clicked` | publish | empty object | user-driven; option-gated | string stable | `packages/node_modules/@webex/widget-recents/src/events.js` | + +`packages/node_modules/@webex/widget-space/src/events.js` declares `calls:memberships:*` constants, and `packages/node_modules/@webex/widget-meet/src/events.js` declares unprefixed `memberships:*` constants. No publisher for those membership constants was found in current Space/Meet/Message source, so they are definition-only compatibility evidence and are excluded from the active event table. + +Legacy event guides also describe notification and mention events that are not present in the current Space/Recents event constant sets. They remain protected historical sources, not current contracts. + +### Commands & Flags + +| Contract ID | Owner | Command | Args / flags | Exit behavior | Compatibility | Defined at | +|---|---|---|---|---|---|---| +| `rw.cmd.install` | build-release-tooling | `npm install --legacy-peer-deps` | lockfile and legacy-peer resolution | nonzero on install error | CI dependency-install surface | `.circleci/config.yml` | +| `rw.cmd.build` | build-release-tooling | `npm run build {target}` | yargs target and command-specific args | nonzero on build error | root script surface | `package.json`, `scripts/build/index.js` | +| `rw.cmd.build-all` | build-release-tooling | `npm run build:all` | none | nonzero if a package build fails | root script surface | `package.json` | +| `rw.cmd.build-package` | build-release-tooling | `npm run build:package {name}` | package name | nonzero on unknown/build failure | root script surface | `package.json`, `scripts/build/commands/dist.js` | +| `rw.cmd.build-widgets` | build-release-tooling | `npm run build:widgets` | none | nonzero on widget build failure | root script surface | `scripts/build/commands/widgets.js` | +| `rw.cmd.build-journey` | build-release-tooling | `npm run build journey {path}` | target output path | nonzero on copy/build failure | browser-test artifact surface | `scripts/build/commands/journey.js` | +| `rw.cmd.serve` | build-release-tooling | `npm run serve {target}` | demo/package/samples target | nonzero on config/server failure | root script surface | `scripts/start/index.js` | +| `rw.cmd.start` | build-release-tooling | `npm start` | none | serves widget demo | root script surface | `package.json` | +| `rw.cmd.jest` | test-automation | `npm run jest [-- Jest args]` | Jest selectors/options | Jest exit code | root test surface | `package.json`, `jest.config.json` | +| `rw.cmd.static-analysis` | test-automation | `npm run static-analysis` | none | ESLint exit code | required gate | `package.json`, `.eslintrc.js` | +| `rw.cmd.journeys` | test-automation | `npm run test:automation[:smoke|:space|:recents]` | environment selects browser/Sauce | WebdriverIO exit code | environment-dependent | `package.json`, `wdio.conf.js` | +| `rw.cmd.tap` | test-automation | `npm run test:tap`, `npm run test:integration` | environment/target selection | WebdriverIO exit code | production/integration validation surface | `package.json`, `wdio.conf.js` | +| `rw.cmd.publish` | build-release-tooling | `npm run publish:components` | CI/registry environment | nonzero on build/publish failure | protected release operation | `package.json`, `scripts/utils/publish.js` | +| `rw.cmd.release` | build-release-tooling | `npm run release` | standard-version args | nonzero on version/changelog failure | Conventional Commits | `package.json` | + +## Requires — what this repo depends on + +| Dependency | What is consumed | Detail | Availability assumption | Fallback | Version floor | +|---|---|---|---|---|---| +| React/ReactDOM | component/rendering contract | `package.json` | host/build provides compatible runtime | none | `^16.8.4` | +| Webex SDK plugin family | auth, rooms, people, conversations, Mercury, meetings, teams, search, flags, metrics | `package.json` | network and valid credentials available for live behavior | loading/error UI; supplied SDK instance supported | `^2.60.4` family | +| Momentum UI / Webex Components | visual and calling component primitives | `package.json` | package resolution and compatible styles | none | pinned root versions | +| Browser DOM/media APIs | mount/events/audio/video/notifications/storage | module source | supported browser and permissions | error/disabled states; test fakes | browser matrix in `babel.config.js` | +| npm/AWS/CDN/CI/Sauce | artifact distribution and verification | `.circleci/config.yml`, `wdio.conf.js` | authenticated CI environment | local unit/static tests only; never claim remote validation | configured CI tool versions | + +## Compatibility & Deprecation Policy + +- **Breaking-change rule:** removing/renaming a package export, required prop, event string/payload guarantee, browser/data API, root command, or artifact path requires an approved spec delta, consumer migration note, and compatible major/versioned rollout. +- **Deprecation:** prefer additive optional props/exports; retain `@ciscospark/*` notices and browser alias while supported, and mark replacements before removal. + +## Detailed Interface Docs + +- Exact package declarations live in each `packages/node_modules/@webex/{package}/src/index.js|ts` and adjacent PropTypes/TypeScript files. +- Detailed behavior, state, flow, failure modes, and tests live in `ai-docs/modules/*-spec.md`. +- Current event construction lives in Space/Recents `src/events.js`; protected event guides are reconciled source material, not a substitute for current constants. + +## Maintenance + +- Add/change/remove a public surface only with synchronized updates to this catalog, the owning module spec, `.sdd/manifest.json`, and native entrypoint/type/event sources. +- Keep deprecated names in `GLOSSARY.md` and current runtime state in `SERVICE_STATE.md`. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/GETTING_STARTED.md b/ai-docs/GETTING_STARTED.md new file mode 100644 index 000000000..1819dfb8a --- /dev/null +++ b/ai-docs/GETTING_STARTED.md @@ -0,0 +1,59 @@ +# Getting Started — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), route through [`SPEC_INDEX.md`](SPEC_INDEX.md), and use this page for the local build/test loop. + +## Prerequisites + +- Node.js 22 LTS (`nvm use` reads `lts/jod` from `.nvmrc`) and npm; CI currently uses Node 22.22 with npm 10. +- Git and network access to npm. Webex integration credentials are required only for authenticated demos/journeys; Sauce credentials are required only for remote browser runs. + +## Clone & Install + +```bash +git clone https://github.com/webex/react-widgets.git +cd react-widgets +nvm use +npm install --legacy-peer-deps +``` + +`npm install --legacy-peer-deps` matches `.circleci/config.yml`; `CONTRIBUTING.md` also documents root `npm install`. + +## Build / Run / Test + +| Task | Command | +|---|---| +| Build all packages | `npm run build:all` | +| Run local Space + Recents demo | `npm start` | +| Unit tests | `npm run jest` | +| Static analysis | `npm run static-analysis` | +| Combined static analysis + Jest | `npm test` | +| Build one package bundle | `npm run build:package {package-name}` | +| Serve one package | `npm run serve:package {package-name}` | +| Browser smoke journeys | `npm run test:automation:smoke` | + +## First-Run Verification + +- `npm run static-analysis` must complete with no ESLint errors. +- `npm run jest` must pass the Jest suites rooted under `packages/node_modules/`. +- `npm start` should serve the widget demo at `http://localhost:8000`; authenticated widget behavior additionally requires valid Webex configuration. + +## Configuration & Secrets + +- Start from `.env.default` only as a key/reference list. Local `.env` may provide Webex client/token/destination values and Sauce or deployment credentials. +- Never commit populated `WEBEX_ACCESS_TOKEN`, `WEBEX_CLIENT_SECRET`, `SAUCE_ACCESS_KEY`, `NETLIFY_ACCESS_TOKEN`, npm tokens, AWS credentials, or private signing material. +- The Webex SDK/test helpers consume service URLs from environment configuration; use team-approved integration/test environments. + +## Dev Environment + +- `npm start` runs `npm run serve demo widget-demo` through the Babel/yargs start tooling. +- `npm run serve:package {name}` uses `scripts/webpack/webpack.dev.babel.js` and webpack-dev-server. +- Journey runs can start a local static server through `wdio.conf.js`; `SAUCE=true` switches to Sauce Labs and Sauce Connect. + +## Where to Go Next + +- Agent rules: `../AGENTS.md`; system shape: `ARCHITECTURE.md`; module routing: `SPEC_INDEX.md`. +- Build details: `modules/build-release-tooling-spec.md`; test setup: `modules/test-automation-spec.md`; conventions: `patterns/` and `rules/`. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/GLOSSARY.md b/ai-docs/GLOSSARY.md new file mode 100644 index 000000000..e0f4a3edb --- /dev/null +++ b/ai-docs/GLOSSARY.md @@ -0,0 +1,59 @@ +# Glossary — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md) and [`SPEC_INDEX.md`](SPEC_INDEX.md). Use these code-grounded terms rather than inventing synonyms. + +## Domain Terms + +| Term | Definition | Authoritative location | Notes / synonyms to avoid | +|---|---|---|---| +| Widget | A self-contained React capability composed with Webex runtime/state integrations and optionally registered for browser/data APIs. | `packages/node_modules/@webex/webex-widget-base/src/index.js` | Not every `react-component-*` package is a widget. | +| Space | The client representation of a Webex room/conversation used by Space and Recents behavior. | `packages/node_modules/@webex/redux-module-spaces/src/reducer.js` | Current public events still use the compatibility resource name `rooms`. | +| Activity | A normalized conversation item such as a message, share, or system activity. | `packages/node_modules/@webex/redux-module-activities/src/reducer.js` | Do not use “message” for every activity type. | +| Destination | A typed widget target identified by email, user ID, space ID, SIP address, or PSTN value where supported. | `packages/node_modules/@webex/widget-space/src/constants.js` | Preserve exact destination strings: `email`, `userId`, `spaceId`, `sip`, `pstn`. | +| Browser global API | `window.webex.widget(element)` and registered `{name}Widget` functions used to mount/remove widgets and receive events. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | `window.ciscospark` is a compatibility alias, not the preferred name. | +| Data API | Auto-mount behavior driven by a `data-toggle="webex-{name}"` element and other `data-*` options. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withDataAPI.js` | Do not confuse with Webex REST APIs. | +| Mercury | The Webex SDK realtime channel used for conversation/activity events. | `packages/node_modules/@webex/redux-module-mercury/src/actions.js` | Not a queue owned by this repository. | +| SDK instance | A supplied or constructed authenticated Webex JavaScript SDK object used by thunks and adapters. | `packages/node_modules/@webex/react-redux-spark/src/sdk.js` | Older code often names the variable `sparkInstance`; it still represents Webex SDK state. | +| Basic mode | Recents loading through Webex REST rather than encrypted conversation flows. | `packages/node_modules/@webex/widget-recents/src/enhancers/setup.js` | The source docs explicitly warn that this removes end-to-end encryption. | +| Journey test | A WebdriverIO browser integration scenario covering mounted widgets against local/TAP/integration environments. | `test/journeys/`, `wdio.conf.js` | Not a Jest unit test. | +| SRI manifest | Versioned CDN file catalog containing integrity hashes and signatures for distributable files. | `scripts/utils/sri.js` | Not `.sdd/manifest.json`. | + +## Abbreviations & Acronyms + +| Abbreviation | Expansion | Meaning in this repo | +|---|---|---| +| SDK | Software Development Kit | Webex JavaScript SDK and calling component adapters consumed by widgets. | +| SRI | Subresource Integrity | Hash/signature metadata generated for CDN widget artifacts. | +| HOC | Higher-Order Component | A React wrapper that injects state, SDK behavior, scrolling, or host integration. | +| TAP | Test Against Production | Journey suites that use deployed CDN artifacts rather than local bundles. | +| PSTN | Public Switched Telephone Network | A supported calling destination type in Space/Meet/Meetings/number-pad flows. | +| SIP | Session Initiation Protocol | A string destination type passed to SDK calling/meeting behavior; this repo does not implement SIP framing. | + +## Context-Specific Meanings + +| Term | Context / module | Meaning here | +|---|---|---| +| `store` | Redux packages | The in-memory Redux state tree. | +| `store` | browser runtime | `window.webex.widgetStore`, a registry of mounted widget objects. | +| `event` | widget contract | A host callback/DOM/ampersand notification such as `messages:created`. | +| `event` | SDK/realtime flow | A Webex SDK/Mercury notification consumed internally and sometimes translated to a host event. | +| `meeting` / `call` | Meetings packages | SDK meeting object plus Redux IDs/media readiness. | +| `meeting` / `call` | legacy media packages | Legacy media/call records and events used by Space/Recents/Meet. | + +## Deprecated / Renamed Terms + +| Old term | Current term | Why renamed | Still appears in | +|---|---|---|---| +| Cisco Spark | Webex | Product/package namespace migration. | Protected `@ciscospark/*` compatibility READMEs and legacy identifiers. | +| `@ciscospark/*` | `@webex/*` | npm package namespace migration. | `packages/node_modules/@ciscospark/*/README.md`, Jest compatibility mappings. | +| `sparkInstance` / `sparkState` | Webex SDK instance/state | Historical source naming retained for compatibility. | legacy widget, Redux, and container code. | +| rooms | spaces | Current product terminology; event contract strings remain `rooms:*`. | event constants and payload fields. | + +## Maintenance + +- Add a term in the same change that introduces a public entity, event, state, destination, or build artifact. +- Cross-reference public surfaces in `CONTRACTS.md` and client state ownership in `ARCHITECTURE.md`. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/REVIEW_CHECKLIST.md b/ai-docs/REVIEW_CHECKLIST.md new file mode 100644 index 000000000..faf1c4510 --- /dev/null +++ b/ai-docs/REVIEW_CHECKLIST.md @@ -0,0 +1,48 @@ +# Review-Check Catalog — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), route through [`SPEC_INDEX.md`](SPEC_INDEX.md), and apply this catalog before merge. + +Each finding records severity, check id, file path, impact, and a concrete fix. Any Blocking finding fails the gate. + +## Core checks (always run) + +| # | Check | What it verifies | Severity if it fails | +|---|---|---|---| +| C1 | Spec-currency + WHAT/WHY | Behavior/public-surface changes update SDD docs and every requirement states WHAT and WHY. | Blocking | +| C2 | Contract correctness | Package exports, props, widget/data/browser APIs, events, commands, and dependencies are complete and compatible. | Blocking | +| C3 | Code-vs-spec match | Signatures, event strings, state/flow, and architecture claims match source and tests. | Blocking | +| C4 | Test adequacy | Positive and negative unit coverage exists; relevant journey/accessibility coverage is run or its gap recorded. | Important | +| C5 | Error handling + input validation | Host/SDK inputs are validated and promise/event/UI failure paths are not swallowed. | Important | +| C6 | Security baseline | No secrets; credential, rendered-content, file/media, encrypted-mode, and release controls remain intact. | Blocking | + +## Coverage-conditional checks + +| # | Check | When it applies | What it verifies | Severity | +|---|---|---|---|---| +| K1 | Regression guard | Any Partial/Untracked module or MODIFIED/REMOVED guarantee | A characterization/targeted baseline protects unchanged behavior with positive and negative cases. | Blocking | +| K2 | Grounding | Partial/Untracked module | Claims cite real source/tests and uncovered surfaces remain explicit. | Important | +| K3 | Drift threshold | Any tracked module | Drift remains within the manifest state threshold. | Important | +| K4 | Coverage-state accuracy | Coverage state changes | Score, gaps, drift, promotion history, and waiver rules support the state. | Medium | + +## Cross-cutting checks + +| # | Check | What it verifies | Severity | +|---|---|---|---| +| X1 | Independent review | Validator runtime differs from generator runtime as `.sdd/manifest.json` requires. | Blocking | +| X2 | Observability | SDK/Redux/metrics/CI signals cover the change without sensitive logging. | Medium | +| X3 | Rollout safety | Package/CDN compatibility, SRI, default props/flags, and consumer transition/rollback are safe. | Important | + +## How the set is selected + +1. Run all six core checks. +2. Add coverage checks for every touched module; all bootstrap modules are currently Partial. +3. Add cross-cutting checks for public contracts, auth/security, async/realtime behavior, build/release, or higher-autonomy work. + +## Output + +- Produce a draft compliance matrix, severity-sorted findings, and Pass / Pass-with-warnings / Blocked verdict. +- Keep local validation drafts ignored and uncommitted; a human decides whether anything is posted externally. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/RULES.md b/ai-docs/RULES.md new file mode 100644 index 000000000..a30050425 --- /dev/null +++ b/ai-docs/RULES.md @@ -0,0 +1,102 @@ +# Rules — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), route through [`SPEC_INDEX.md`](SPEC_INDEX.md), and use detailed rules under `rules/` only when relevant. + +## Coverage Map (which docs/specs to trust) + +| Module | Manifest coverage state | What it means here | +|---|---|---| +| Space and messaging | Partial | High first-pass coverage; independent validation passed, but code cross-checking and the history promotion gate still apply. | +| Recents | Partial | High first-pass coverage; verify event names/payloads against current constants and listeners. | +| Meetings | Partial | High first-pass coverage; verify SDK lifecycle behavior and known unimplemented lifecycle branches in code. | +| Calling widgets | Partial | High first-pass coverage; verify typed adapter/component contracts and sparse tests. | +| Shared UI components | Partial | Package entrypoints are indexed; cross-check component props and adjacent tests. | +| Redux and state management | Partial | Actions/reducers are indexed; cross-check each thunk and state record before behavior changes. | +| Containers and HOCs | Partial | Entrypoints and flows are documented; cross-check wrapped-component contracts. | +| Widget runtime/auth/demos | Partial | Host/auth contracts are detailed; resolve the default-helper discrepancy before relying on that shortcut. | +| Build and release tooling | Partial | Commands and artifact flow are documented; validate CI/deployment changes against current scripts. | +| Test automation | Partial | Suites and intent are reconciled; verify environment-dependent journeys in their target environment. | + +## Autonomy & Ask-First + +- **May proceed:** docs-only corrections, tests for already documented behavior, and internal refactors that preserve exported contracts after an approved plan. +- **Ask first / plan + confirm:** package exports, component props, events, data/browser globals, SDK auth, state shape, destination behavior, build/publish/deploy flow, or test-policy changes. +- **Never without explicit human approval:** push, publish, deploy, delete/overwrite protected docs, rotate credentials, or intentionally break a consumer contract. + +## Naming + +- Published packages use the `@webex/` namespace and kebab-case package directories; legacy `@ciscospark/` names are compatibility references only. +- React component exports use PascalCase; HOCs/hooks use `withX`, `injectX`, or `useX`; Redux action constants are upper snake case with a module prefix in their string values. +- Public widget event strings use `{resource}:{event}` lowercase names such as `messages:created`; never rename a string while only changing its constant identifier. + +## Logging + +- Use the supplied SDK logger for product diagnostics and existing Redux logging only in non-production stores. +- Never log credentials, private message/file content, or raw call/media objects. Preserve the Recents call-object omission when enriching event logs. +- Logging rules are review-enforced; ESLint prevents stray patterns only where configured. + +## Error Handling + +- Async Redux/SDK operations must reject or dispatch explicit error/status actions; UI boundaries render `ErrorDisplay` or typed error states. +- Do not swallow promise failures. Where legacy code currently logs-and-continues (for example optional flags), preserve that behavior unless a spec change approves stricter propagation. +- Calling adapters/hooks must handle missing adapter functions and promise failures without leaving loading/playing/form state stuck. + +## Imports / Dependencies + +- Follow ESLint `import/order`: builtin, external, internal, parent, sibling, index with blank lines between groups. +- Widgets may depend on base/runtime, containers, Redux modules, and components; reusable components must not acquire widget-specific state ownership without an approved boundary change. +- Add or upgrade dependencies only at root `package.json`; verify package output externalization and Webex SDK version-family compatibility. + +## Testing + +- Add positive and negative unit coverage near changed package source; Jest discovers `*.test.js` under `packages/node_modules/`. +- Changes to Space/Recents host behavior, auth, calls, files, roster, accessibility, or startup options identify and run the appropriate journey suite. +- Required local gates are `npm run static-analysis` and `npm run jest`; environment-dependent WebdriverIO gaps must be reported, not silently treated as passing. + +## Security + +- Treat props, data attributes, SDK/realtime payloads, file/card content, and environment variables as boundary inputs. +- Never hardcode or log secrets. Preserve encrypted default flows and SRI generation; see `SECURITY.md`. + +## Spec-Currency & Drift Thresholds + +- Update the owning module spec, `CONTRACTS.md`/`SERVICE_STATE.md` when applicable, `SPEC_INDEX.md`, and `.sdd/manifest.json` in the same behavior-changing change. +- Drift thresholds: Specced ≤5%, Partial ≤15%, Untracked ≤25%. Promotion additionally follows `.sdd/coverage-policy.defaults.yaml`. + +## Secrets Policy + +- Secrets are injected by the host, `.env`, or CI secret stores. `.env` is ignored; `.env.default` contains names/default service URLs only. +- Never commit tokens, keys, client secrets, deployment credentials, private signing keys, or populated test-user data. + +## Concurrency & Async + +- Promise thunks and SDK event listeners must keep loading/connecting flags consistent on success and failure. +- Register realtime/media/browser listeners once and remove them on teardown; do not duplicate host events during React lifecycle updates. +- Preserve event ordering assumptions: store normalized state before consumers render or host callbacks observe the corresponding transition. + +## Strict-Compliance Mode + +- CI, release, SDD validation, and automated changes stop on the first Blocking contract, security, source-fidelity, conformance, or code/spec finding. +- Independent validator findings remain local drafts until a human approves publication. + +## Enforcement Metadata + +| Rule area | Source evidence | Severity | Owner | Verification | +|---|---|---|---|---| +| Public naming and entrypoints | `packages/node_modules/@webex/widget-space/src/index.js`, `packages/node_modules/@webex/widget-speed-dial/src/index.ts`, `packages/node_modules/@webex/widget-space/src/events.js`, `package.json` | Blocking for incompatible public changes | owning package maintainers | contract review, static analysis, relevant package tests | +| Logging and credential handling | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js`, `packages/node_modules/@webex/widget-recents/src/events.js`, `.eslintrc.js` | Blocking for secret exposure; Important otherwise | runtime and capability maintainers | security review and static analysis | +| Async error handling | `packages/node_modules/@webex/redux-module-spaces/src/actions.js`, `packages/node_modules/@webex/widget-meetings/src/handlers/index.js` | Important; Blocking when a public operation falsely reports success | owning state/widget maintainers | positive/negative Jest coverage and relevant journeys | +| Dependency boundaries | `.eslintrc.js`, `package.json`, `scripts/utils/package.js` | Important | repository/tooling maintainers | ESLint, build-target verification, compatibility review | +| Testing and accessibility | `jest.config.json`, `wdio.conf.js`, `test/journeys/testplan.md` | Blocking for required gate failure | repository and owning module maintainers | `npm run static-analysis`, `npm run jest`, focused journey/axe suite | +| Secrets and release operations | `.gitignore`, `.circleci/config.yml`, `scripts/build/commands/sri.js` | Blocking | security and release maintainers | secret scan/review, CI release gates, no local publish/deploy verification | +| Concurrency and cleanup | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`, `packages/node_modules/@webex/widget-voice-mail/src/hooks/useAudio.ts`, `packages/node_modules/@webex/widget-space/src/container.js` | Important; Blocking for credential/resource leakage | owning runtime/module maintainers | lifecycle tests, listener audit, repeated mount/unmount characterization | +| Spec currency and strict compliance | `.sdd/manifest.json`, `ai-docs/REVIEW_CHECKLIST.md` | Blocking when configured thresholds or validation gates fail | repository maintainers | drift check, conformance, coverage review, independent validator | + +## Maintenance + +- Add a rule only when code/history shows a recurring repository constraint; defer formatting/import mechanics to ESLint. +- Cross-reference examples in `patterns/` and fuller rules in `rules/`. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-23`. diff --git a/ai-docs/SECURITY.md b/ai-docs/SECURITY.md new file mode 100644 index 000000000..7946fe393 --- /dev/null +++ b/ai-docs/SECURITY.md @@ -0,0 +1,73 @@ +# Security Baseline — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), [`SPEC_INDEX.md`](SPEC_INDEX.md), and [`ARCHITECTURE.md`](ARCHITECTURE.md). Read this before changing authentication, host input, SDK calls, rendered content, or deployment credentials. + +## Trust Boundaries + +| Boundary | Untrusted side | Trusted side | Enforced at the crossing | +|---|---|---|---| +| React widget props | embedding application | widget/runtime | PropTypes or TypeScript props; destination/setup validation; error UI for invalid state. | +| Browser/data API | DOM attributes, host callbacks, selected element | `webex-widget-base` | Element check, known widget registration, data attribute normalization, component contracts. | +| Credentials/SDK | host-supplied token, guest token, or SDK instance | SDK-backed widget state | Webex SDK auth/device setup; never treat presence of a string as authorization. | +| Realtime/network data | Webex services and Mercury events | Redux/modules/components | SDK processing, resource normalization, action/reducer boundaries, render encoding. | +| Adaptive Card/markdown/file content | message/file payloads | rendered browser UI/download | parsing helpers, supported-action checks, React rendering, file retrieval helpers. | +| CI/release environment | secret variables and external registries/cloud | build/publish/deploy scripts | CI secret injection; no credentials in tracked files or logs. | + +## Authentication & Authorization Model + +- **Authentication:** legacy widgets accept `accessToken`, `guestToken`, or an already authenticated SDK instance; SDK setup lives under `packages/node_modules/@webex/react-redux-spark/`. +- **Authorization:** Webex services/plugins enforce scopes and resource access. The widget repository selects SDK operations but does not implement a separate RBAC/ABAC layer. +- **Default posture:** without authenticated/registered SDK state, setup enhancers do not start resource loading or Mercury flows. Evidence: `packages/node_modules/@webex/widget-space/src/enhancers/setup.js`. + +## Secret & Credential Handling + +- Secrets come from the embedding host or CI/local environment, never source code. +- Runtime props inject access/guest tokens or SDK objects; `.env` supports local/test/build tooling and is ignored. +- Rotation policy is owned by the credential issuer/CI platform; this repository contains no rotation implementation. +- **Hard rule:** never commit or log tokens, client secrets, Sauce keys, npm tokens, Netlify tokens, AWS credentials, or private signing keys. + +## Data Classification & Handling + +| Data class | Examples | Storage rule | Logging rule | In transit | +|---|---|---|---|---| +| Credentials | access/guest tokens, client secret, service keys | host/CI memory only; no tracked files | never log | Webex/CI HTTPS mechanisms | +| User/conversation data | names, email addresses, IDs, messages, files, memberships | transient Redux/React/SDK state; remote service remains system of record | avoid payload logs; redact credentials and sensitive content | SDK-managed HTTPS/Mercury | +| Call/media data | call objects, media streams, voicemail audio | browser memory/media elements | do not serialize raw call/media objects | SDK/browser media transport | +| Build/release metadata | versions, hashes, signatures, artifact URLs | generated build/CDN manifests | safe unless paired with credentials | registry/CDN HTTPS | + +## Input Validation & Output Encoding Posture + +- Validate destination types/IDs, props, event payload assumptions, and adapter availability before SDK operations. +- Keep React rendering/escaping intact. Do not insert untrusted HTML except through the existing reviewed markdown/adaptive-card paths. +- Restrict file handling to SDK/file helper outputs; preserve filename/download and supported-card-action validation. + +## Transport & Headers + +- The library consumes Webex and deployment services over their SDK/HTTPS clients; it does not own an HTTP server or CORS policy. +- CDN hosts must preserve SRI usage and HTTPS URLs. Local demo/browser test servers are development-only and do not define production security headers. + +## Session & Cookie Posture + +- Production widget packages do not establish a server session or own authentication cookies. +- The private demo stores configuration, including an access token, through `react-cookie` in `packages/node_modules/@webex/widget-demo/src/components/demo-widget/index.js`; this is development-only behavior, not a secure production token-storage contract. +- Hosts must not copy demo cookie persistence into production integrations. Production credentials stay in the embedding application's approved credential lifecycle and should be supplied through an authenticated SDK instance when possible. + +## Known Sensitive Areas & Accepted Risks + +| Area | Risk | Mitigation / current posture | Owner | +|---|---|---|---| +| Data-attribute token configuration | Credentials can be visible in DOM/source | Prefer imported React/SDK-instance integration for sensitive hosts; never place real tokens in committed examples. | embedding application | +| Demo cookie persistence | The private demo persists its access token in a browser cookie without defining production cookie flags/rotation | Keep the demo private and development-only; clear cookies in automated journeys; never use this storage design in a production host. | widget runtime maintainers | +| Legacy browser global alias | Global mutable namespace can be modified by other scripts | Validate target element, register once with warning, and remove store entries on teardown. | widget runtime maintainers | +| Recents `basicMode` | REST loading removes end-to-end encryption behavior | Default is `false`; document and intentionally opt in. | Recents consumers | +| Legacy/default base helper discrepancy | README quick start and current implementation disagree | Treat current code as source of truth; prefer object-shaped `constructWebexEnhancer` until separately fixed and validated. | widget runtime maintainers | +| Client logging | SDK/event payloads may contain user data | Use SDK levels, omit raw call object from Recents event logs, and never log tokens. | module maintainers | + +## Reporting & Review + +- Authentication, rendered untrusted content, browser globals, file/media handling, dependencies, and release-secret changes require security-aware review plus static analysis and relevant tests. +- Report suspected vulnerabilities through the Webex developer support/security process; do not disclose credentials or exploit details in public issues. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/SERVICE_STATE.md b/ai-docs/SERVICE_STATE.md new file mode 100644 index 000000000..1eead9def --- /dev/null +++ b/ai-docs/SERVICE_STATE.md @@ -0,0 +1,59 @@ +# Service State (living) — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md), [`SPEC_INDEX.md`](SPEC_INDEX.md), and [`ARCHITECTURE.md`](ARCHITECTURE.md). Despite the template name, this is the library's current as-built surface registry. + +## Current Events + +| Event / topic | Direction | Producer/consumer | Payload reference | +|---|---|---|---| +| `messages:created` | publish to host | Space and Recents | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rooms:unread`, `rooms:read` | publish to host | Space and Recents | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | +| `rooms:selected` | publish to host | Recents | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `calls:created` | publish to host | Space/Recents legacy calling flow | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-recents/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | +| `calls:connected`, `calls:disconnected` | publish to host | Space | `packages/node_modules/@webex/widget-space/src/events.js` | +| `memberships:created`, `memberships:deleted` | publish to host | Recents | `packages/node_modules/@webex/widget-recents/src/events.js` | +| `activity:changed` | publish to host | Space | `packages/node_modules/@webex/widget-space/src/events.js` | +| `add:clicked`, `profile:clicked`, `user_signout:clicked` | publish to host | Recents | `packages/node_modules/@webex/widget-recents/src/events.js` | +| Webex Mercury activities | consume | Redux Mercury/Space/Recents packages | `packages/node_modules/@webex/redux-module-mercury/src/actions.js` | +| meeting `media:ready` | consume | meetings Redux package | `packages/node_modules/@webex/redux-module-meetings/src/actions.js` | + +Space declares `calls:memberships:*` constants and legacy Meet declares `memberships:*` constants, but no publisher for either membership set was found in current source. They are intentionally excluded from this current-events registry. + +## Data Stores + +| Store | Purpose | Owned by this library? | +|---|---|---| +| per-widget Redux store | immutable client resource/status state | yes, in-memory only | +| `window.webex.widgetStore` | mounted browser-widget registry | yes, in-memory only | +| browser `localStorage` focus flags | number-pad focus handoff | yes, temporary keys only | +| Webex service data | conversations, spaces, users, teams, calls, meetings | no; consumed through SDK/services | + +## External Dependencies + +| Dependency | Used for | Timeout / retry | Circuit breaker / fallback | +|---|---|---|---| +| Webex SDK/services | auth, registration, rooms/conversations, people, teams, meetings, realtime, search, flags, metrics | SDK/plugin-owned; widget thunks expose promise state | loading/error UI; optional features may log and continue | +| Browser media/notification APIs | calls, audio/video, notifications | browser/SDK-owned | permission or capability failures surface through UI/status | +| npm registry | published package delivery | npm tooling | build/publish fails; no silent fallback | +| AWS S3/CloudFront | CDN widget artifacts | CircleCI/orb tooling | deployment job fails; archives remain versioned | +| Sauce Labs/Selenium | browser journeys | WebdriverIO retries connections up to three times | local Selenium when Sauce is disabled | + +## Feature Flags (current) + +| Flag / option | Gates | Current default | Owner | Safe to remove when | +|---|---|---|---|---| +| Recents `basicMode` | REST vs encrypted initial-space loading | `false` | Recents | only after a contract change removes the alternate mode | +| `enableAddButton` | Recents add-space control/event | `false` | Recents | after consumer migration and major-version removal | +| `enableSpaceListFilter` | Recents filter UI | `true` | Recents | after consumer migration and major-version removal | +| `enableUserProfile` / `enableUserProfileMenu` | Recents profile controls/events | `true` / `false` | Recents | after consumer migration and major-version removal | +| Space `spaceActivities` / `composerActions` | activity and composer controls | enabled | Space | after consumer migration and major-version removal | +| SDK feature/flag values | Webex-service-driven behavior | service-defined | Webex services | only with upstream contract approval | + +## Maintenance + +- Update the relevant row in the same change that changes an export, event, host option, state store, dependency, or feature flag. +- Stable catalog: `CONTRACTS.md`; security posture: `SECURITY.md`; module details: `modules/`. + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-22`. diff --git a/ai-docs/SPEC_INDEX.md b/ai-docs/SPEC_INDEX.md new file mode 100644 index 000000000..49b983046 --- /dev/null +++ b/ai-docs/SPEC_INDEX.md @@ -0,0 +1,64 @@ +# Spec Index — react-widgets + +> Start with root [`AGENTS.md`](../AGENTS.md). This is the human router; `.sdd/manifest.json` is the machine source of truth. + +## Module Registry + +| Module | Responsibility | Manifest coverage state | Start here | +|---|---|---|---| +| Space and messaging | Space, message, files, roster, activity composition, and host events. | Partial (94%) | `modules/space-messaging-spec.md` | +| Recents | Space-list loading/filtering, realtime updates, selection/call/profile host events. | Partial (95%) | `modules/recents-spec.md` | +| Meetings | Destination lookup, create/join/media/leave lifecycle, and meeting UI. | Partial (92%) | `modules/meetings-spec.md` | +| Calling widgets | Call history, number pad, speed dial, voicemail, typed adapters/hooks. | Partial (91%) | `modules/calling-spec.md` | +| Shared UI components | Public/private presentational components and UI utilities. | Partial (93%) | `modules/shared-ui-components-spec.md` | +| Redux and state management | Immutable state, SDK thunks, reducers, selectors, metrics, fixtures. | Partial (94%) | `modules/state-management-spec.md` | +| Containers and HOCs | State/SDK-connected UI, downloads, notifications, presence, scrolling, Mercury. | Partial (92%) | `modules/containers-hooks-spec.md` | +| Widget runtime/auth/demos | Auth/SDK setup, browser/data APIs, teardown, sign-in, demos, samples. | Partial (93%) | `modules/widget-runtime-auth-spec.md` | +| Build and release tooling | Build/transpile/bundle/SRI/serve/publish/deploy/release workflows. | Partial (95%) | `modules/build-release-tooling-spec.md` | +| Test automation | Jest, journeys, smoke/TAP/integration/accessibility, CI test reporting. | Partial (96%) | `modules/test-automation-spec.md` | + +All scores were assessed 2026-07-22 from package entrypoints, events/commands, implementation flows, tests, and reconciled sources. Independent Cursor validation passed on 2026-07-23. Status remains Partial because documented weak-evidence gaps and the five-PR promotion-history gate remain. + +## Task Routing + +| If the task is… | Load | +|---|---| +| Understanding the system | `ARCHITECTURE.md` | +| Changing a package/widget behavior | the owning `modules/*-spec.md` plus `CONTRACTS.md` | +| Changing host/browser/data API or auth | `modules/widget-runtime-auth-spec.md` + `SECURITY.md` | +| Changing exported events | owning widget spec + `CONTRACTS.md` + `SERVICE_STATE.md` | +| Changing build/release/CDN behavior | `modules/build-release-tooling-spec.md` + `SECURITY.md` | +| Changing tests or CI verification | `modules/test-automation-spec.md` + `REVIEW_CHECKLIST.md` | +| Updating docs after code | affected module specs, standing indexes, manifest, and source-fidelity policy if protected docs are involved | + +## Incident History + +No tracked incident/RCA index exists in this repository. Do not infer incidents from generic warnings; add reference rows only when an authoritative ticket/RCA is supplied. + +## Phase-Based Loading Protocol + +| Phase | Load | +|---|---| +| Orient | `AGENTS.md` + this file | +| Specify | affected module spec, `CONTRACTS.md`, and focused standing docs | +| Build | affected spec + `patterns/`/`rules/` + real source/tests | +| Verify | `REVIEW_CHECKLIST.md`, affected specs, manifest, and independent validation report | + +## Spec Registry + +| Doc | Location | Purpose | +|---|---|---| +| Architecture | `ARCHITECTURE.md` | system/package shape, state, interactions, host and release boundaries | +| Patterns | `patterns/` | code-grounded correct/incorrect conventions | +| Rules | `RULES.md` + `rules/` | enforceable repository constraints | +| Glossary | `GLOSSARY.md` | domain language and compatibility names | +| Security | `SECURITY.md` | trust boundaries, credentials, rendered data, release secrets | +| Contracts | `CONTRACTS.md` | package, event, browser/data API, and command catalog | +| Service state | `SERVICE_STATE.md` | living events/stores/dependencies/flags registry | +| Getting started | `GETTING_STARTED.md` | install/build/run/test loop | +| Decisions | `adr/` | durable architecture decisions | +| Review catalog | `REVIEW_CHECKLIST.md` | merge gate selection and finding format | + +--- + +Provenance: generated_by `codex-desktop`; approved_by `repository owner`; updated_at `2026-07-23`. diff --git a/ai-docs/adr/0001-centralize-capability-sdd-docs.md b/ai-docs/adr/0001-centralize-capability-sdd-docs.md new file mode 100644 index 000000000..2392c3506 --- /dev/null +++ b/ai-docs/adr/0001-centralize-capability-sdd-docs.md @@ -0,0 +1,37 @@ +# ADR-0001 — Centralize capability-grouped SDD documentation + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md) · [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +| Field | Value | +|---|---| +| Status | Accepted | +| Date | 2026-07-22 | +| Deciders | repository owner | +| Supersedes / Superseded by | none | +| Generated from | `adr` @ SDLC template library `0.2.1` | + +## Context + +The repository contains 103 package roots under one Git/build boundary. There was no canonical `ai-docs/` tree, and package-by-package documentation would fragment behaviors that span widgets, components, containers, reducers, SDK adapters, tooling, and tests. Evidence: `package.json`, `packages/node_modules/@webex/`, `scripts/`, `test/journeys/`. + +## Decision + +Keep one root `AGENTS.md`, centralized standing docs under `ai-docs/`, and ten capability-grouped canonical specs under `ai-docs/modules/`. `.sdd/manifest.json` is the machine router and `ai-docs/SPEC_INDEX.md` is its human mirror. + +## Alternatives Considered + +| Alternative | Pros | Cons | Why rejected | +|---|---|---|---| +| One spec per package | Precise source locality | More than 100 specs; cross-package widget flows become fragmented and costly to load/update | Does not match the approved maintenance capabilities | +| Source-local capability docs | Docs near source | Capability groups span many sibling package roots; no single honest source directory owns each group | Central routing is clearer for this package forest | +| Standing docs only | Small initial footprint | Omits module flows, requirements, errors, tests, and public-surface detail | Fails rigorous SDD and coverage goals | + +## Consequences + +- **Positive:** future agents can load one capability spec plus focused standing docs; cross-package contracts have one owner. +- **Negative / cost:** module specs must maintain package lists and cannot rely on directory locality for discovery. +- **Agents must:** update the capability spec, indexes/contracts, and manifest together when package ownership or public behavior changes. + +## Revisit When + +- The repository splits into independent Git/build/release units, or a capability grows large enough to require an approved module-map change. diff --git a/ai-docs/adr/0002-reconcile-existing-documentation.md b/ai-docs/adr/0002-reconcile-existing-documentation.md new file mode 100644 index 000000000..6c65e3db5 --- /dev/null +++ b/ai-docs/adr/0002-reconcile-existing-documentation.md @@ -0,0 +1,37 @@ +# ADR-0002 — Reconcile existing documentation without replacement + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md) · [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +| Field | Value | +|---|---| +| Status | Accepted | +| Date | 2026-07-22 | +| Deciders | repository owner | +| Supersedes / Superseded by | none | +| Generated from | `adr` @ SDLC template library `0.2.1` | + +## Context + +The root README, package READMEs, widget event guides, and journey test plan contain install, compatibility, event, and test intent, but some statements predate current packages/constants. Replacing or deleting them would discard review history; treating all of them as current would propagate drift. + +## Decision + +Preserve protected source documents unchanged. Reorganize supported meaning into canonical SDD sections, use current source/tests to resolve conflicts, and keep unit-level disposition in ignored source-fidelity inventories. Canonical specs do not embed full-file snapshots. + +## Alternatives Considered + +| Alternative | Pros | Cons | Why rejected | +|---|---|---|---| +| Keep sources separate only | No migration effort | Agents still lack a canonical, code-checked route | Does not meet root-level SDD goal | +| Move/delete old docs | One apparent source | Loses history and breaks existing links | Violates the explicit preservation requirement | +| Copy old docs verbatim into `ai-docs/` | Fast | Reproduces stale statements and wrong section shape | Fails source-fidelity and conformance rules | + +## Consequences + +- **Positive:** reviewed source intent remains available while canonical docs describe current behavior. +- **Negative / cost:** future edits to protected source material require another fidelity/reconciliation pass. +- **Agents must:** never overwrite protected sources as a shortcut; resolve contradictions explicitly and cite stable code/test paths. + +## Revisit When + +- A human owner explicitly retires a protected document and approves its link/consumer migration. diff --git a/ai-docs/adr/README.md b/ai-docs/adr/README.md new file mode 100644 index 000000000..8edcd93e6 --- /dev/null +++ b/ai-docs/adr/README.md @@ -0,0 +1,12 @@ +# Architecture Decision Records + +Durable, append-only decisions for react-widgets. Accepted ADRs are superseded by a new numbered ADR rather than edited to reverse their meaning. + +## Use ADRs For + +- Understanding why centralized capability documentation and reconcile policy were selected. +- Recording a future durable architecture/process decision with alternatives and consequences. +- Knowing when a prior decision should be revisited without rewriting its history. + +- `0001-centralize-capability-sdd-docs.md` — central root documentation with capability-grouped module specs. +- `0002-reconcile-existing-documentation.md` — preserve legacy docs and reconcile them against current code/tests. diff --git a/ai-docs/modules/build-release-tooling-spec.md b/ai-docs/modules/build-release-tooling-spec.md new file mode 100644 index 000000000..f03bb7dad --- /dev/null +++ b/ai-docs/modules/build-release-tooling-spec.md @@ -0,0 +1,244 @@ +# Build and Release Tooling — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `build-release-tooling` | +| Source path(s) | `scripts/`, root build configs, `.circleci/config.yml`, package metadata | +| Doc kind | Module spec | +| Coverage score | 95% assessed 2026-07-22; commands, selection rules, outputs, CI promotion, and failure gates covered | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Root scripts, yargs command handlers, package metadata, bundler/transpiler configuration, lockfile, and CircleCI are authoritative. `scripts/release.sh` explicitly says it is not currently used and is historical, not the current delivery contract. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Repository usage guide development commands | local build/test | verified | Public Surface and Use Cases. | +| `package.json` scripts | command API | authoritative | Public Surface and Requirements. | +| build/publish scripts and CI | artifacts/release | authoritative | Design, rules, and failure handling. | +| Historical release helper | historical release approach | reference-only | Pitfalls; do not execute as the current workflow. | + +## Overview + +Root yargs CLIs discover packages beneath `packages/node_modules`, transpile package sources, build widget bundles, generate journey distributions and SRI metadata, start local demos/samples, and publish eligible packages. CircleCI installs with Node 22.22, runs lint/Jest and browser journeys, versions the repository, publishes packages, and deploys selected widget assets. + +## Purpose / Responsibility + +Turn the single Git repository into consistent per-package artifacts and enforce quality/promotion rules without changing package source semantics. + +## Stack + +Node.js 22.22 in CI, npm 10, Babel, Webpack 4, Rollup 2, Sass/PostCSS, yargs, SRI signing, standard-version, npm publishing, CircleCI, AWS S3/CloudFront, Netlify tooling, Jest, and WebdriverIO. + +## Folder / Package Structure + +```text +package.json, package-lock.json +babel.config.js, webpack.config.babel.js, rollup*.js +scripts/ +├── build/commands/ # all, components, dist, esm, journey, package.json, sri, transpile, widgets +├── start/commands/ # demo, package, samples +├── publish/commands/ # components, package +├── deploy/commands/ +├── tooling/commands/ +└── utils/ # build, package discovery, dependencies, publish, SRI, process helpers +.circleci/config.yml +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `package.json` | supported npm command surface | +| `scripts/build/index.js` and `commands/*.js` | build targets and selection | +| `scripts/utils/build.js` | Babel/Webpack/Rollup execution | +| `scripts/utils/package.js` | package discovery/classification | +| `scripts/publish/commands/*.js` | publish eligibility and dispatch | +| `.circleci/config.yml` | install, test, build, version, publish, CDN deployment | +| `rollup.config.js`, `rollup.calling-config.js`, `scripts/webpack/*` | artifact configuration | + +## Public Surface + +Repository command contracts: + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.cmd.install` | CLI | `npm install --legacy-peer-deps` | CI dependency installation | lockfile/CI contract | `.circleci/config.yml` | `../CONTRACTS.md` | +| `rw.cmd.build` | CLI | `npm run build {target}` plus root build aliases | package/widget artifacts and integrity | preserve yargs target names/arguments | `package.json`, `scripts/build/index.js`, `scripts/build/commands/` | `../CONTRACTS.md` | +| `rw.cmd.build-journey` | CLI | `npm run build journey {path}` | static browser-test assets | target path is destructive/generated | `scripts/build/commands/journey.js` | `../CONTRACTS.md` | +| `rw.cmd.serve` | CLI | `npm run serve {target}` / `npm start` | local development hosts | development only | `package.json`, `scripts/start/` | `../CONTRACTS.md` | +| `rw.cmd.publish` | CLI | `npm run publish:components` | publish eligible non-private packages | credentialed; never a casual verification command | `scripts/publish/commands/components.js`, `package.json` | `../CONTRACTS.md` | +| `rw.cmd.release` | CLI | `npm run release` | update version/changelog through standard-version | credentialed/release-only | `package.json` | `../CONTRACTS.md` | + +Compatibility notes: + +Command names and required arguments are automation interfaces. Build output is generated and excluded from source-of-truth documentation. + +## Requires (dependencies) + +Locked npm dependencies, a supported Node/npm runtime, package `name`/`private` metadata, filesystem/process access, CI credentials for npm/AWS/upstream Git, and a private key plus `widget-key.pub` for SRI. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `BUILD-R-001` | Package discovery and classification use package metadata; private packages are omitted from public ES/component publishing and demos are excluded from component publication. | Prevent accidental release of internal/example code. | `scripts/utils/package.js`, `scripts/build/commands/components.js`, `scripts/publish/commands/components.js` | `scripts/tooling/commands/check-testable.js`, `.circleci/config.yml` | No dedicated publish dry-run suite found. | PRESENT | +| `BUILD-R-002` | Public widgets receive distributable bundles while packages receive transpiled/module outputs appropriate to the selected command. | Consumers use both embeddable bundles and package imports. | `scripts/build/commands/all.js`, `scripts/build/commands/widgets.js`, `scripts/utils/build.js` | `.circleci/config.yml` | Artifact snapshots are limited. | PRESENT | +| `BUILD-R-003` | Journey builds copy static servers/axe and build Space, Recents, Demo, and current calling distributions into the requested target. | Browser tests require a self-contained served tree. | `scripts/build/commands/journey.js` | `.circleci/config.yml` | Some calling output paths share `dist-call-history`; validate before altering. | PRESENT | +| `BUILD-R-004` | SRI generation fails safely when `PRIVATE_KEY` is absent and signs package distributions with the repository public key when present. | CDN consumers need verifiable assets without exposing signing material. | `scripts/build/commands/sri.js`, `scripts/utils/sri.js` | `.circleci/config.yml` | Secret provisioning is external. | PRESENT | +| `BUILD-R-005` | CI gates release on dependency install, lint, Jest, built journey assets, and configured Chrome/Firefox journeys before version/publish/deploy. | Published packages and CDN assets must derive from a verified commit. | `.circleci/config.yml` | CI workflow itself | External services/credentials can block. | PRESENT | +| `BUILD-R-006` | Version/publish changes preserve package dependency/version consistency and publish only non-private eligible packages. | The repository releases many interdependent packages from one versioned source. | `scripts/utils/deps.js`, `scripts/publish/commands/components.js`, `.circleci/config.yml` | `package.json` | Rollback is operational/manual. | PRESENT | + +## Design Overview + +Root npm aliases dispatch to small yargs command handlers. Package utilities enumerate the nonstandard `packages/node_modules` tree; build helpers centralize Babel, Rollup, and Webpack invocation. CI persists dependencies and built journey assets between jobs, then versions/publishes/deploys from the tested repository state. + +## Data Flow + +```mermaid +flowchart LR + Source[Package source + metadata] --> Discover[Package discovery] + Discover --> Transpile[Babel CJS/ES] + Discover --> Bundle[Webpack/Rollup bundles] + Bundle --> SRI[SRI metadata] + Transpile --> NPM[npm packages] + Bundle --> Journey[Journey static tree] + Journey --> BrowserTests[WDIO] + NPM --> Publish[npm publish] + SRI --> CDN[S3/CloudFront] +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| package artifact build | Artifact build | invalid target/build failure stops artifact production | +| CI verify, version, publish, deploy | Verified promotion | failed required gate prevents promotion; partial external failure requires operator review | + +```mermaid +sequenceDiagram + participant D as Developer/CI + participant Y as Build yargs command + participant P as Package discovery + participant B as Babel/Webpack/Rollup + D->>Y: target + package/path + Y->>P: enumerate and filter metadata + P-->>Y: eligible package paths + loop each selected package + Y->>B: transpile/bundle + alt build succeeds + B-->>D: generated artifact + else invalid target or compiler failure + B-->>D: nonzero/throw; no promotion + end + end +``` + +```mermaid +sequenceDiagram + participant C as CircleCI + participant N as npm/build CLIs + participant T as Tests + participant R as Registries/CDN + C->>N: install locked dependencies + C->>T: lint + Jest + C->>N: build journey assets + C->>T: Chrome/Firefox integration suites + alt all required jobs pass + C->>N: version + production builds + SRI + C->>R: push upstream/tag, publish npm, deploy CDN + else failure + C-->>R: no promotion + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class NpmScripts + class YargsCommands + class PackageDiscovery + class BuildHelpers + class CircleCI + class ArtifactTargets + NpmScripts --> YargsCommands + YargsCommands --> PackageDiscovery + YargsCommands --> BuildHelpers + CircleCI --> NpmScripts + BuildHelpers --> ArtifactTargets +``` + +## Use Cases + +- Build every public widget and transpile every package for a release candidate. +- Build one package during focused development. +- Generate the static journey tree consumed locally or in CI. +- Publish all eligible component/module packages while skipping private/demo packages. +- Produce and deploy versioned Space/Recents/Demo CDN assets with SRI. + +## Business Rules & Invariants + +- `private: true` packages and demo packages are never published by the bulk component command. +- Build output is generated; source remains under package `src`. +- Release promotion uses the tested commit/version and requires credentials from CI, never committed files. +- SRI private keys come only from the environment. + +## Concurrency & Reactive Flow + +CI jobs share cached dependencies and persisted journey artifacts. Package loops may invoke builds sequentially but external publish/deploy operations have partial-failure risk; reruns must not silently publish a different source commit under the same version. + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| build/transpile failure | command throws/non-zero process | fix source/config and rerun from clean output | +| command argument missing | yargs usage error or false result | supply documented target/package | +| SRI key missing | logged error and false result | provision `PRIVATE_KEY` through approved secret store | +| registry/CDN/Git failure | CI job failure or partial publish | inspect versions/artifacts before controlled retry | + +## Pitfalls + +- `scripts/release.sh` deletes/rebuilds large trees and declares itself unused; do not run it as the current release procedure. +- The repository layout resembles installed `node_modules` but is tracked source. +- `build journey` contains repeated calling-output destinations and asynchronous copy/build sequencing that merits characterization before refactoring. +- Bulk publish currently catches errors; review logs to detect partial publication. + +## Module Do's / Don'ts + +- Do preserve private/demo filters and verify artifacts from a clean checkout. +- Do keep secrets in environment/CI stores. +- Don't publish or deploy from an unvalidated local worktree. +- Don't change package discovery or artifact paths without exercising every build target. + +## Key Design Trade-off + +Central scripts give 100+ packages one consistent release pipeline, but the custom tracked-node_modules layout and mixed Babel/Webpack/Rollup outputs make broad tooling changes high blast radius. + +## Test-Case Strategy (module) + +| Requirement | Current evidence | Focused gap | +|---|---|---| +| `BUILD-R-001` filters | `scripts/build/commands/components.js`, `scripts/publish/commands/components.js` | dry-run eligibility snapshot | +| `BUILD-R-002` artifacts | `scripts/build/commands/all.js`, `.circleci/config.yml` | clean artifact manifest | +| `BUILD-R-003` journey tree | `scripts/build/commands/journey.js`, `.circleci/config.yml` | calling destination assertions | +| `BUILD-R-004` SRI | `scripts/build/commands/sri.js`, `.circleci/config.yml` | missing/malformed key tests | +| `BUILD-R-005` gates | `.circleci/config.yml` | local workflow lint/schema check | +| `BUILD-R-006` version/publish | `scripts/publish/commands/components.js`, `.circleci/config.yml` | partial publish recovery runbook | + +## Traceability + +- Commands and external boundaries: `../GETTING_STARTED.md`, `../CONTRACTS.md`. +- Security/review rules: `../SECURITY.md`, `../REVIEW_CHECKLIST.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/calling-spec.md b/ai-docs/modules/calling-spec.md new file mode 100644 index 000000000..b7776672b --- /dev/null +++ b/ai-docs/modules/calling-spec.md @@ -0,0 +1,317 @@ +# Calling Widgets — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `calling` | +| Source path(s) | `packages/node_modules/@webex/widget-call-history/`, `widget-number-pad/`, `widget-speed-dial/`, `widget-voice-mail/` | +| Doc kind | Module spec | +| Coverage score | 91% assessed 2026-07-22; all typed entrypoints, adapter/call/audio/form flows, UI state and risks covered; automated tests are sparse outside GenericModal | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +TypeScript entrypoints/interfaces, adapter contexts/hooks, component implementations, stories, and the one current Jest test are evidence. Placeholder package READMEs do not establish behavior beyond package identity/import examples. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Call History and Speed Dial placeholder READMEs | package identity/import | verified/unsupported | Valid package identity is in Public Surface; empty install sections and generic summaries are unsupported by code and not promoted. | +| Generic Modal README | component import | verified | The current internal modal relationship is documented in Public Surface and UI Flow. | +| Stories and TypeScript types | API/examples | used/reference-only | Exact props remain native type/story detail; stable exported concepts are summarized below. | + +## Overview + +Calling is a newer TypeScript/React component family separate from the legacy enhanced widgets. Call History renders call records and call-back controls; Number Pad handles digits, long-press behavior, contact/call selection, and keyboard focus; Speed Dial renders/sorts/creates/edits/removes callable records; Voice Mail renders messages and accessible audio playback/scrubbing. + +Packages use Webex component-adapter interfaces and React contexts/hooks to make calls or fetch/search data without constructing the legacy Redux/Webex runtime. Consumers supply typed data, callbacks, adapters, translation providers, and styles. + +## Purpose / Responsibility + +Own reusable calling-oriented UI and interaction state for history, dialing, speed dials, and voicemail. It does not own calling service persistence, call signaling, contact storage, or voicemail storage. + +## Stack + +TypeScript 4.5, React hooks/contexts, Momentum UI collaboration components, Webex component adapter interfaces, react-i18next, react-hook-form, react-stately/React Aria utilities, Sass, Storybook, and limited Jest. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── widget-call-history/src/ # list/item types, call hook, date/voiceover helpers +├── widget-number-pad/src/ # dial pad, call buttons, contact search/popover, focus hooks +├── widget-speed-dial/src/ # list/items/forms/search/modals/photo/banner and types +└── widget-voice-mail/src/ # voicemail item, playback controls, scrubbing/audio hooks +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/widget-call-history/src/index.ts`, `packages/node_modules/@webex/widget-number-pad/src/index.ts`, `packages/node_modules/@webex/widget-speed-dial/src/index.ts`, `packages/node_modules/@webex/widget-voice-mail/src/index.ts` | public exports | +| `packages/node_modules/@webex/widget-call-history/src/CallHistoryItem.types.ts` | call-history record contract | +| `packages/node_modules/@webex/widget-speed-dial/src/SpeedDials.types.tsx` | speed-dial records/events/list props | +| `packages/node_modules/@webex/widget-speed-dial/src/SpeedDialForm.types.tsx` | create/edit form types | +| package `src/contexts/AdapterContext.tsx` | supplied adapter contracts | +| `packages/node_modules/@webex/widget-number-pad/src/utils/WebexDialPad.ts` | long-press/focus constants | +| `packages/node_modules/@webex/widget-voice-mail/src/hooks/useAudio.ts` | audio playback state/listener lifecycle | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.widget.call-history` | React/TypeScript | `CallHistoryItem`, `CallHistory`, `NoHistory`, item types | render/select/call back history records | public semver/type surface | `packages/node_modules/@webex/widget-call-history/src/index.ts` | `../CONTRACTS.md` | +| `rw.widget.number-pad` | React/TypeScript | `NumberPad`, `CallButtons`, contact/search/popover components | digit entry, calling controls, contact selection | public semver/type surface | `packages/node_modules/@webex/widget-number-pad/src/index.ts` | `../CONTRACTS.md` | +| `rw.widget.speed-dial` | React/TypeScript | `SpeedDials`, item/form/search/banner + form/list types | manage and activate speed-dial records | public semver/type surface | `packages/node_modules/@webex/widget-speed-dial/src/index.ts` | `../CONTRACTS.md` | +| `rw.widget.voice-mail` | React/TypeScript | `VoicemailItem`, playback controls, scrubbing bar | render/play/seek/call back voicemail | public semver/type surface | `packages/node_modules/@webex/widget-voice-mail/src/index.ts` | `../CONTRACTS.md` | + +Compatibility notes: + +- Callback names, record fields, optional/default behavior, focus/keyboard semantics, and exported TypeScript types are public. +- Internal hooks/contexts not re-exported at package `index.ts` are implementation surfaces unless a consumer deep-import already exists. + +## Requires (dependencies) + +- React host plus translation/theme/style providers expected by components. +- `@webex/component-adapter-interfaces`, `@webex/sdk-component-adapter`, Momentum UI/Webex Components. +- Host-supplied record arrays, callbacks/adapters, audio source URLs, refs, and permissions. +- Browser Audio, localStorage, MutationObserver, keyboard/focus, and file/image APIs for applicable controls. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `CALLING-R-001` | Call History renders supplied records in order, preserves selected state, forwards row/call actions, and can dismiss a badge on row activation. | The package is a controlled list; hosts own records and follow-up call behavior. | `packages/node_modules/@webex/widget-call-history/src/CallHistory.tsx`, `packages/node_modules/@webex/widget-call-history/src/CallHistoryItem.types.ts` | `packages/node_modules/@webex/widget-call-history/src/CallHistory.stories.tsx` | No Jest test found. | PRESENT | +| `CALLING-R-002` | Number Pad reports button values, supports configured long-press alternatives, keyboard grid focus, and contact/call selection callbacks. | Dial input and keyboard accessibility must be deterministic. | `packages/node_modules/@webex/widget-number-pad/src/NumberPad.tsx`, `packages/node_modules/@webex/widget-number-pad/src/hooks/useGridFocus.ts`, `packages/node_modules/@webex/widget-number-pad/src/utils/WebexDialPad.ts` | `packages/node_modules/@webex/widget-number-pad/src/NumberPad.stories.tsx` | No Jest test found. | PRESENT | +| `CALLING-R-003` | Speed Dial renders records and forwards press/audio/video/edit/remove/add/sort callbacks with typed record shapes. | Hosts/adapters own persistence and calls; UI must not invent side effects. | `packages/node_modules/@webex/widget-speed-dial/src/SpeedDials.tsx`, `packages/node_modules/@webex/widget-speed-dial/src/SpeedDials.types.tsx` | `packages/node_modules/@webex/widget-speed-dial/src/SpeedDialModal.stories.tsx` | Core list/form tests sparse. | PRESENT | +| `CALLING-R-004` | Speed Dial forms validate/create/edit inputs and keep modal/search/photo/error UI state consistent on cancel/submit/failure. | Invalid or stuck form state creates bad callable records and inaccessible modal flow. | `packages/node_modules/@webex/widget-speed-dial/src/SpeedDialForm.tsx`, `packages/node_modules/@webex/widget-speed-dial/src/SpeedDialForm.types.tsx`, `packages/node_modules/@webex/widget-speed-dial/src/GenericModal/GenericModal.tsx` | `packages/node_modules/@webex/widget-speed-dial/src/GenericModal/GenericModal.test.tsx` | Full validation matrix not covered. | WEAK | +| `CALLING-R-005` | Voicemail playback loads audio, reports play state/time/duration, supports seek and focus transitions, and removes audio listeners on cleanup. | Audio controls must remain synchronized and accessible across repeated items/mounts. | `packages/node_modules/@webex/widget-voice-mail/src/VoicemailPlaybackControls.tsx`, `packages/node_modules/@webex/widget-voice-mail/src/hooks/useAudio.ts` | `packages/node_modules/@webex/widget-voice-mail/src/ScrubbingBar.stories.tsx` | No Jest test found. | PRESENT | +| `CALLING-R-006` | Adapter-backed call/search operations handle missing adapters and rejected promises without committing service state locally. | These packages are UI adapters, not service owners; failures must remain recoverable by the host. | `packages/node_modules/@webex/widget-call-history/src/contexts/AdapterContext.tsx`, `packages/node_modules/@webex/widget-call-history/src/hooks/useMakeCall.ts`, `packages/node_modules/@webex/widget-speed-dial/src/hooks/useContactSearch.ts` | `packages/node_modules/@webex/widget-call-history/src/CallHistory.stories.tsx` | Error contract needs stronger tests. | WEAK | + +## Design Overview + +The packages are controlled component layers. Record arrays and callbacks are supplied by consumers, while small hooks provide local focus, debounce, form, audio, or adapter-call state. Adapter contexts decouple UI from a specific SDK instance and allow a host to inject the component-adapter implementation. + +The shared `useWebexClasses` pattern creates predictable BEM-like classes, and Storybook stories serve as executable usage examples. TypeScript entrypoints intentionally export selected components/types rather than every internal modal/hook. + +## Data Flow + +```mermaid +flowchart LR + Host[Records, callbacks, adapters, translations] --> Components[Calling React components] + Components --> Local[Hook/form/focus/audio state] + Components --> Adapter[Context adapter or host callback] + Adapter --> Services[Webex calling/contact/voicemail services] + Services --> Host + Host --> Components + Components --> Browser[Audio, keyboard, focus, localStorage, observer] +``` + +Remote operations use host callbacks or adapter promises; component composition is in-process React; audio/focus interactions use browser APIs. + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| select/call record or speed dial | Controlled action | missing callback/adapter and rejection | +| create/edit speed dial | Form/modal flow | validation/cancel/service failure | +| play/seek voicemail | Audio flow | load/end/unmount recovery | + +```mermaid +sequenceDiagram + participant U as User + participant C as History/Dial/SpeedDial UI + participant H as Host callback or adapter + participant S as Calling service + U->>C: select or press audio/video call + alt callback/adapter available + C->>H: record/address + call mode/label + H->>S: make call + alt rejected + S-->>H: error + H-->>C: recoverable error state + else accepted + S-->>H: call result + end + else unavailable + C-->>U: no service side effect; host must configure adapter + end +``` + +```mermaid +sequenceDiagram + participant U as User + participant M as SpeedDial modal + participant F as Form + participant H as Host/adapter + U->>M: add or edit + M->>F: initialize values + alt invalid + F-->>U: validation errors; submit disabled + else valid submit + F->>H: create/update record + alt failure + H-->>M: error modal/banner; form retained + else success + H-->>M: close and host refreshes items + end + end + opt cancel + U->>M: cancel; discard local edits + end +``` + +```mermaid +sequenceDiagram + participant U as User + participant V as Voicemail controls + participant A as HTMLAudioElement + U->>V: play + V->>A: play() + A-->>V: time/duration/end events + U->>V: seek or pause + V->>A: set currentTime or pause() + alt audio ends + A-->>V: playing=false + focus update + else component unmounts + V->>A: remove listeners/pause cleanup + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class AdapterProvider + class CallHistory + class NumberPad + class SpeedDials + class SpeedDialForm + class VoicemailItem + class VoicemailPlaybackControls + class ScrubbingBar + AdapterProvider --> CallHistory + AdapterProvider --> SpeedDials + AdapterProvider --> VoicemailItem + SpeedDials --> SpeedDialForm + VoicemailItem *-- VoicemailPlaybackControls + VoicemailPlaybackControls *-- ScrubbingBar +``` + +## Use Cases + +- **UC-1 Review and return a call:** host supplies history → user selects/calls an item → callback/adapter initiates call → host updates records/selection. +- **UC-2 Dial a number/contact:** user enters digits or searches contacts → keyboard/long-press rules update input → call selection invokes supplied action. +- **UC-3 Manage speed dials:** user adds/edits/removes/reorders records → validated callback/adapter request → host refreshes controlled list. +- **UC-4 Play/call back voicemail:** user plays/seeks/pauses an audio source or presses call-back → audio state/focus updates and supplied call action runs. +- **UI flow:** list/empty/error → selected item/action controls; Speed Dial additionally uses add/edit/search/photo/modal/error screens; Voice Mail uses play/pause/scrub/focus states. +- **Cross-service flow:** adapters/callbacks cross to Webex component services; all persisted results return from the host rather than being owned in component state. + +## State Model + +- Controlled data: call history items, selected record, speed-dial records, voicemail metadata, and host callbacks/adapters. +- Local state: focus/keyboard indices, digit press timing, search/debounce, form validity/dirty state, modal selection/error, image preview, audio object/current time/duration/playing. +- Browser focus flags are temporary and removed on blur/unmount; they are not domain persistence. + +## Business Rules & Invariants + +- Stable item IDs drive React keys/selection/reordering; do not use array position as durable identity. +- Speed Dial submit remains disabled when invalid and, for edits, when not dirty. +- Long-press behavior applies only to configured dial-pad values and must not also emit the short value after the threshold. +- Audio play/pause/time and focus state must reflect the same `HTMLAudioElement` lifecycle. + +## Concurrency & Reactive Flow + +- Debounced search and adapter calls can resolve out of order; callers/hooks must avoid applying stale results. +- Audio time/end events update React state asynchronously and must remove listeners on cleanup. +- Timers implement dial-pad long press; cleanup must prevent delayed callbacks after unmount/release. +- MutationObserver/focus listeners in call-selection UI require symmetric removal. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Idle + Idle --> Editing: add/edit speed dial + Editing --> Invalid: validation fails + Invalid --> Editing: input corrected + Editing --> Saving: valid submit + Saving --> Idle: host confirms and refreshes + Saving --> Error: adapter rejects + Error --> Editing: retry + Idle --> Playing: play voicemail + Playing --> Paused: pause/seek + Paused --> Playing: resume + Playing --> Idle: ended/unmounted +``` + +## UI Flow + +- Call History: list/empty → row selected → audio/video/callback action. +- Number Pad: focus grid → digit/long press → optional contact results → call-mode selection. +- Speed Dial: list/error/empty → add/edit modal → validated form/search/photo → save/cancel/error → reordered list. +- Voice Mail: item → playback controls → scrub/focus/ended states, with call-back action when provided. +- Preserve labels, focus visibility, arrow-key transitions, list position announcements, and German/locale-specific formatting flags. + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| no callback/adapter function | optional call is not performed | provide the required callback/AdapterProvider | +| adapter/search/save rejects | hook/form error or host rejection | retain input, show error/banner/modal, retry/cancel | +| invalid speed-dial input | form errors/disabled submit | correct required fields/type/addresses | +| audio fails to load/play | missing duration/play rejection | show non-playing state and provide valid source/browser permission | +| empty record list | empty/list UI | host supplies records or user adds when supported | +| component unmount with active timer/listener/audio | leak/stale callback risk | cleanup timer/listener/observer/audio in effect return | + +## Pitfalls + +- Package READMEs are placeholder-level and contain empty install commands; use entrypoints/types/stories, not those blanks. +- Several packages deep-import adapter interface distribution paths (`dist/cjs` or `dist/esm`); dependency upgrades can break type/runtime resolution. +- `dismissBagdeonClickRow` is misspelled in the public Call History type/implementation; correcting spelling alone would be breaking. +- Number Pad uses localStorage for focus coordination, not persisted dialing data; always remove temporary keys. +- Calling packages have far less Jest coverage than legacy component packages; characterize before behavior changes. + +## Module Do's / Don'ts + +- DO keep components controlled and forward service operations through typed callbacks/adapters. +- DO preserve focus cleanup and accessible announcements when changing markup. +- DON'T introduce hidden persistence, silently rename misspelled public props, or export internal hooks without a contract review. + +## Export Stability + +All symbols in the four `src/index.ts` files and their exported TypeScript interfaces are public semver surfaces. Additive optional props/types are preferred; removals/required-field changes need a major migration. The placeholder READMEs do not narrow these actual exports. + +## Host Integration & Theming + +Consumers mount React components, supply Momentum/Webex styling and i18n context, and inject data/callbacks or adapter providers. Components assume browser focus/audio/observer APIs where used but do not assume the legacy `window.webex.widget` runtime. + +## Key Design Trade-off + +- Controlled components and adapter contexts maximize host portability and keep service state outside the UI packages, at the cost of requiring consumers to wire records, persistence, translations, and failure handling explicitly. + +## Test-Case Strategy (module) + +Storybook stories cover visual/use-case examples; `GenericModal.test.tsx` covers the only discovered Jest target. Before modifying behavior, add React tests for each exported component/hook boundary with positive action and negative missing/rejected/cleanup cases, plus keyboard/focus/audio assertions. Adapter integration belongs in a host/component-adapter test layer. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `CALLING-R-001` Call History | `packages/node_modules/@webex/widget-call-history/src/CallHistory.stories.tsx` | no Jest coverage | +| `CALLING-R-002` Number Pad | `packages/node_modules/@webex/widget-number-pad/src/NumberPad.stories.tsx` | long-press/grid/contact Jest coverage | +| `CALLING-R-003` Speed Dial callbacks/sort | `packages/node_modules/@webex/widget-speed-dial/src/SpeedDialModal.stories.tsx` | core list tests | +| `CALLING-R-004` form/modal | `packages/node_modules/@webex/widget-speed-dial/src/GenericModal/GenericModal.test.tsx` | validation/save/error matrix | +| `CALLING-R-005` voicemail audio | `packages/node_modules/@webex/widget-voice-mail/src/ScrubbingBar.stories.tsx` | audio event/cleanup/focus tests | +| `CALLING-R-006` adapters/errors | `packages/node_modules/@webex/widget-call-history/src/hooks/useMakeCall.ts` | missing/rejected adapter tests | + +## Traceability + +- Architecture: `../ARCHITECTURE.md`; registry: `../SPEC_INDEX.md`; contracts: `../CONTRACTS.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/containers-hooks-spec.md b/ai-docs/modules/containers-hooks-spec.md new file mode 100644 index 000000000..0847ab64d --- /dev/null +++ b/ai-docs/modules/containers-hooks-spec.md @@ -0,0 +1,246 @@ +# Containers and HOCs — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `containers-hooks` | +| Source path(s) | `packages/node_modules/@webex/react-container-*/`, `react-hoc-*/` | +| Doc kind | Module spec | +| Coverage score | 92% assessed 2026-07-22; every package and lifecycle boundary covered, with gaps around repeated subscription cleanup | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Connected-container exports, selectors, actions/reducers, lifecycle methods, tests, and consuming widgets determine behavior. Despite the capability name, the repository currently implements class/connect HOCs rather than a general React Hooks layer. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Legacy container/HOC rename READMEs | namespace migration | verified | Export Stability records the suffix-preserving `@webex` mapping. | +| Container/HOC source | injection and lifecycle | authoritative | Public Surface through Concurrency. | +| Adjacent Jest tests | projections/interactions | verified | Test-Case Strategy. | + +## Overview + +Seven container packages connect shared UI to Redux/SDK-aware behavior: activity list, file downloader, message composer, notifications, presence avatar, read receipts, and scrolling activity. Two HOCs add conversation Mercury handling and imperative scroll behavior. + +## Purpose / Responsibility + +Translate application state and actions into presentational props, and own only the subscriptions or DOM capabilities necessary for that translation. Containers must preserve wrapped-component contracts and clean up resources they create. + +## Stack + +React 16, react-redux `connect`, Redux actions/reducers, Immutable.js, reselect, recompose conventions, PropTypes, Webex SDK/Mercury, CSS modules, and Jest. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── react-container-activity-list/src/ +├── react-container-file-downloader/src/ +├── react-container-message-composer/src/ +├── react-container-notifications/src/ +├── react-container-presence-avatar/src/ +├── react-container-read-receipts/src/ +├── react-container-scrolling-activity/src/ +├── react-hoc-conversation-mercury/src/ +└── react-hoc-scrollable/src/ +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/react-container-activity-list/src/index.js` and the exact container paths indexed in `../CONTRACTS.md` | public default/named exports | +| `packages/node_modules/@webex/react-container-activity-list/src/selectors.js` | activity projection | +| `packages/node_modules/@webex/react-container-message-composer/src/container.js` | composer state/actions | +| `packages/node_modules/@webex/react-container-notifications/src/container.js` | notification integration | +| `packages/node_modules/@webex/react-hoc-conversation-mercury/src/index.js` | conversation subscription/filtering | +| `packages/node_modules/@webex/react-hoc-scrollable/src/index.js` | imperative scrolling API | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.containers` | SDK/React | seven `react-container-*` default entrypoints | connect presentation to selected state/actions | public semver; injected/caller props remain compatible | exact `rw.container.*` catalog paths; representative: `packages/node_modules/@webex/react-container-activity-list/src/index.js`, `packages/node_modules/@webex/react-container-message-composer/src/index.js` | `../CONTRACTS.md` | +| `rw.hoc.conversation-mercury` | SDK/React | `wrapConversationMercury(Component)` | filter/dispatch live conversation activity | public semver; `WrappedComponent` and display name are observable | `packages/node_modules/@webex/react-hoc-conversation-mercury/src/index.js` | `../CONTRACTS.md` | +| `rw.hoc.scrollable` | SDK/React | `injectScrollable(Component)` plus imperative scroll methods | reusable activity scrolling | public semver; methods and thresholds are observable | `packages/node_modules/@webex/react-hoc-scrollable/src/index.js` | `../CONTRACTS.md` | + +Compatibility notes: + +Message composer and notifications also expose actions and a reducer. Injected props are implementation-owned; caller-facing props remain declared alongside each wrapper. + +## Requires (dependencies) + +Redux slices/actions/selectors, shared components, React/DOM lifecycle, Webex SDK/Mercury for live conversation behavior, and owning widgets to compose any exported reducers. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `CONT-R-001` | Containers derive presentational props without changing caller-supplied props except for documented injections. | Wrapped components must remain reusable and predictable. | `packages/node_modules/@webex/react-container-activity-list/src/index.js`, `packages/node_modules/@webex/react-container-activity-list/src/selectors.js` | `packages/node_modules/@webex/react-container-activity-list/src/index.test.js`, `packages/node_modules/@webex/react-container-activity-list/src/selectors.test.js` | Prop collision behavior is not uniformly tested. | PRESENT | +| `CONT-R-002` | Containers that export reducers/actions expose them through the package barrel for widget composition. | State behavior must be installable with the connected component. | `packages/node_modules/@webex/react-container-message-composer/src/index.js`, `packages/node_modules/@webex/react-container-notifications/src/index.js` | `packages/node_modules/@webex/react-container-message-composer/src/index.test.js`, `packages/node_modules/@webex/react-container-notifications/src/container.test.js` | None known. | PRESENT | +| `CONT-R-003` | Conversation Mercury handling subscribes only after connection and conversation identity exist and ignores other conversations/replies. | Prevent duplicate or cross-conversation activity updates. | `packages/node_modules/@webex/react-hoc-conversation-mercury/src/index.js` | adjacent HOC tests | Listener removal is not explicit in this HOC. | WEAK | +| `CONT-R-004` | Scrollable exposes stable threshold-based top/bottom methods around the wrapped content node. | Activity views need reusable infinite-scroll and jump-to-bottom behavior. | `packages/node_modules/@webex/react-hoc-scrollable/src/index.js` | adjacent HOC tests | Thresholds 100/150 pixels are current behavior. | PRESENT | +| `CONT-R-005` | File, composer, notification, presence, receipt, and activity projections surface SDK/action failures through owning state/callback paths. | Containers should not swallow failures between data and UI layers. | `packages/node_modules/@webex/react-container-file-downloader/src/index.js`, `packages/node_modules/@webex/react-container-message-composer/src/container.js`, `packages/node_modules/@webex/react-container-notifications/src/container.js` | `packages/node_modules/@webex/react-container-file-downloader/src/index.test.js`, `packages/node_modules/@webex/react-container-message-composer/src/index.test.js` | Error rendering may be owned by parent UI. | PRESENT | + +## Design Overview + +Containers are intentionally thin adapters. Selectors/projectors turn Immutable state into component-friendly data, dispatch bindings supply operations, and the wrapped component remains responsible for presentation. HOCs own cross-cutting lifecycle behavior that cannot be expressed as a simple projection. + +## Data Flow + +```mermaid +flowchart LR + Store[Redux store] --> Selector[mapState/selectors] + Parent[Caller props] --> Container[Connected container/HOC] + Selector --> Container + Container --> View[Shared component] + View -->|callback| Dispatch[bound action] + Dispatch --> Store + SDK[Mercury/SDK] --> HOC[Lifecycle subscription] + HOC --> Dispatch +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| project and dispatch | Container interaction | initial projection and action-result rerender | +| subscribe and filter realtime activity | Mercury conversation flow | other-conversation/reply ignore branch; teardown gap is recorded in Pitfalls | + +```mermaid +sequenceDiagram + participant W as Widget + participant C as Container + participant S as Store + participant V as View + W->>C: caller props + S-->>C: selected state + C->>V: merged presentation props + V-->>C: user callback + C->>S: dispatch action + S-->>V: rerendered projection +``` + +```mermaid +sequenceDiagram + participant H as Mercury HOC + participant M as Mercury + participant R as Conversation reducer + H->>M: subscribe after connected + conversation id + M-->>H: conversation.activity + alt matching conversation and supported object + H->>R: receive activity/comment or participant change + else other conversation or reply + H-->>H: ignore + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class PresentationalComponent + class ConnectedContainer + class ReduxStore + class ConversationMercuryHOC + class ScrollableHOC + ConnectedContainer --> PresentationalComponent + ConnectedContainer --> ReduxStore + ConversationMercuryHOC --> ConnectedContainer + ScrollableHOC --> PresentationalComponent +``` + +## Use Cases + +- Project normalized activities into an activity-list component. +- Submit composer text/files through bound operations and local reducer state. +- Receive a matching Mercury conversation event and dispatch the appropriate update. +- Download a file through SDK-backed state while reporting failure. +- Allow an activity surface to measure, set, and classify its scroll position. + +## State Model + +Most containers are projections with no independent state. Message composer, notifications, and presence-related packages may own reducer slices. HOCs keep only lifecycle/DOM references; the conversation HOC records `isListeningToMercury` in the conversation state to guard registration. + +## Business Rules & Invariants + +- Do not dispatch conversation activity for a different conversation. +- Reply activities are currently ignored by the Mercury HOC. +- Person-object add/leave verbs map to participant actions before the comment is recorded. +- Scroll top/bottom calculations use the wrapped node and current 100/150-pixel thresholds. + +## Concurrency & Reactive Flow + +Redux updates, prop changes, DOM scroll events, and Mercury events are independent. Subscription guards must be idempotent; unmount or conversation changes must not leave callbacks that target stale stores/components. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Waiting + Waiting --> Listening: Mercury connected + conversation id + Listening --> Listening: matching supported event + Listening --> Ignored: other conversation/reply + Ignored --> Listening + Listening --> Disposed: unmount/teardown +``` + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| selected state not loaded | initial/empty projection | render bounded empty/loading state | +| bound SDK/action rejects | owning reducer error or callback result | parent renders error/retry | +| scroll node absent | imperative method cannot complete | call only after mount; guard new call sites | +| duplicate/unremoved Mercury listener | duplicate dispatched activity | dispose listener and characterize reconnect behavior | + +## Pitfalls + +- `react-hoc-conversation-mercury` registers a listener but does not visibly remove it; characterize behavior before changing it. +- `shouldComponentUpdate(nextProps) { return nextProps !== this.props; }` relies on reference changes. +- “containers-hooks” is a capability label, not evidence that these packages export Hooks. + +## Module Do's / Don'ts + +- Do keep selectors deterministic and injected props explicit. +- Do add teardown for newly introduced listeners/timers. +- Don't move presentation decisions into a state connector. +- Don't change imperative scrolling methods without checking activity consumers. + +## Export Stability + +Container and HOC package entrypoints are public semver surfaces. Protected `@ciscospark` notices document the same package suffix under `@webex`; wrapper display names and `WrappedComponent` are observable in tests/tooling. + +## Host Integration & Theming + +Containers pass through host/widget props and render shared components, inheriting their CSS/intl/accessibility behavior. Browser-specific behavior is limited to DOM scrolling, downloads, notifications, and realtime SDK events. + +## Key Design Trade-off + +Thin packages keep concerns reusable, but lifecycle behavior spread across HOCs and widgets makes listener ownership harder to audit. New cross-cutting behavior should state exactly who registers and disposes it. + +## Test-Case Strategy (module) + +| Requirement | Current evidence | Focused gap | +|---|---|---| +| `CONT-R-001` projection | `packages/node_modules/@webex/react-container-activity-list/src/index.test.js`, `packages/node_modules/@webex/react-container-activity-list/src/selectors.test.js` | prop collision matrix | +| `CONT-R-002` exports | `packages/node_modules/@webex/react-container-message-composer/src/index.test.js` | public export snapshot | +| `CONT-R-003` Mercury filters | None found adjacent to `packages/node_modules/@webex/react-hoc-conversation-mercury/src/index.js` | teardown/reconnect characterization | +| `CONT-R-004` scrolling | None found adjacent to `packages/node_modules/@webex/react-hoc-scrollable/src/index.js` | absent-node/resize behavior | +| `CONT-R-005` failures | `packages/node_modules/@webex/react-container-file-downloader/src/index.test.js`, `test/journeys/specs/space/index.js` | integrated error display | + +## Traceability + +- Architecture/contracts: `../ARCHITECTURE.md`, `../CONTRACTS.md`. +- Listener rule: `../rules/clean-up-runtime-listeners.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/meetings-spec.md b/ai-docs/modules/meetings-spec.md new file mode 100644 index 000000000..2b7f30fa4 --- /dev/null +++ b/ai-docs/modules/meetings-spec.md @@ -0,0 +1,296 @@ +# Meetings — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `meetings` | +| Source path(s) | `packages/node_modules/@webex/widget-meet/`, `widget-meetings/`, `redux-module-meetings/` | +| Doc kind | Module spec | +| Coverage score | 92% assessed 2026-07-22; entrypoints, destinations, legacy events, create/join/media/leave flow, state, UI, and current tests covered; lifecycle TODOs remain gaps | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Claims come from package entrypoints, containers/enhancers/handlers, meeting actions/reducer, UI tests, and Space journey meeting intent. The legacy namespace notice supplies compatibility history only. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Protected `@ciscospark/widget-meet` README and rename notice | compatibility | verified | Export Stability records the current namespace; legal material remains protected. | +| Space/Recents call and meeting guidance | host flow/tests | verified | Use Cases and Test Strategy connect incoming/start/join/hang-up behavior to current code. | +| Current implementation comments/TODOs | design gaps | used | Missing media-stopped and meeting stopped/destroyed handling is explicit in Pitfalls and tests. | + +## Overview + +This capability contains two related legacy widgets. `widget-meet` integrates the older media/Mercury call stack and emits call lifecycle host events. Its membership event names are exported constants but have no publisher in current source. `widget-meetings` uses the SDK meetings plugin plus `redux-module-meetings` to create, join, add media, and leave meetings while rendering inactive/active states. + +Both accept typed destination identifiers and compose through the shared widget base. The newer reducer intentionally stores meeting IDs and readiness state rather than SDK meeting objects; the SDK meeting collection remains the object source of truth. + +## Purpose / Responsibility + +Own embeddable meeting/call presentation and the client lifecycle from destination lookup through create/join/media/leave. Remote meeting membership, media transport, and service truth remain owned by the Webex SDK/services. + +## Stack + +JavaScript, React/PropTypes, Redux/Immutable.js, recompose, react-intl, Webex JS SDK meeting/media/Mercury plugins, shared media components, Jest, and Space browser journeys. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── widget-meet/src/ # legacy call widget, media/Mercury enhancers, host events +├── widget-meetings/src/ # create/join/leave UI, selectors, handlers, setup +└── redux-module-meetings/src/ # meeting thunks, ID indexes, media-ready state +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/widget-meet/src/index.js` | legacy widget export, destination types, enhancer composition | +| `packages/node_modules/@webex/widget-meet/src/events.js` | legacy call/membership event strings and payload builder | +| `packages/node_modules/@webex/widget-meetings/src/index.js` | meetings widget export, reducers, destination types | +| `packages/node_modules/@webex/widget-meetings/src/container.js` | public destination props and active/inactive UI wiring | +| `packages/node_modules/@webex/redux-module-meetings/src/actions.js` | create/join/add-media/leave thunks and SDK event binding | +| `packages/node_modules/@webex/redux-module-meetings/src/reducer.js` | destination/locus/ID indexes and meeting readiness state | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.widget.meet` | SDK/React/event | default Meet widget + reducers/destination types | legacy call experience and host call events | public semver | `packages/node_modules/@webex/widget-meet/src/index.js` | `../CONTRACTS.md` | +| `rw.widget.meetings` | SDK/React | default Meetings widget + reducers/destination types | meetings-plugin create/join/media/leave UI | public semver | `packages/node_modules/@webex/widget-meetings/src/index.js` | `../CONTRACTS.md` | +| `rw.state.meetings` | SDK/Redux | action thunks, reducer, `initialState`, `buildDestinationLookup` | shared meeting state and operations | public semver | `packages/node_modules/@webex/redux-module-meetings/src/index.js` | `../CONTRACTS.md` | +| `rw.meet.events` | event | emitted `calls:created/connected/disconnected`; defined-only `memberships:notified/connected/declined/disconnected` constants | legacy host lifecycle notifications and definition-only compatibility names | exact emitted strings stable; membership constants are not active events without a publisher | `packages/node_modules/@webex/widget-meet/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | `../CONTRACTS.md` | + +Compatibility notes: + +- Destination values remain `sip`, `email`, `userId`, `spaceId`, and `pstn`. +- `widget-meet` and `widget-meetings` are distinct public packages; do not silently replace one with the other. + +## Requires (dependencies) + +- Shared widget runtime/auth/intl and Webex SDK meetings/media/Mercury plugins. +- Legacy media Redux package for `widget-meet`; meetings Redux package for `widget-meetings`. +- Browser audio/video/media-stream capability and host permissions. +- Destination context from the host/Space/Recents flow. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `MEET-R-001` | Widgets accept only supported destination types/IDs and expose the same destination string set. | Hosts need one predictable target contract across Space/Meet/Meetings. | `packages/node_modules/@webex/widget-meet/src/index.js`, `packages/node_modules/@webex/widget-meetings/src/index.js`, `packages/node_modules/@webex/widget-meetings/src/container.js` | `test/journeys/specs/space/startup-settings.js`, `test/journeys/specs/space/index.js` | Invalid destination tests are sparse in Meetings. | PRESENT | +| `MEET-R-002` | `createAndJoinMeeting` creates through the SDK, stores ID lookup state, binds media events, joins, and resolves the meeting. | Ordered state creation prevents selectors from referencing an unindexed SDK object. | `packages/node_modules/@webex/redux-module-meetings/src/actions.js` | None found for the action path. | Major unit-test gap. | WEAK | +| `MEET-R-003` | Meeting Redux stores IDs and readiness flags by destination/locus/ID; SDK collection remains object source of truth. | Avoids placing mutable SDK meeting objects into immutable Redux state. | `packages/node_modules/@webex/redux-module-meetings/src/reducer.js` | `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.test.js` | Reducer lacks direct tests. | PRESENT | +| `MEET-R-004` | Adding media obtains streams with explicit send/receive settings and adds them to the located meeting. | Media direction must match host/user intent and SDK lifecycle. | `packages/node_modules/@webex/redux-module-meetings/src/actions.js` | ActiveMeeting/MeetingsWidget tests | Promise rejection path lacks dedicated tests. | PRESENT | +| `MEET-R-005` | Leave locates the meeting, invokes SDK `leave()`, then records `joined=false`; errors reject to the caller/error enhancer. | UI/state must not claim leave before the SDK operation succeeds. | `packages/node_modules/@webex/redux-module-meetings/src/actions.js`, `packages/node_modules/@webex/redux-module-meetings/src/reducer.js`, `packages/node_modules/@webex/widget-meetings/src/handlers/index.js` | `packages/node_modules/@webex/widget-meetings/src/components/ActiveMeeting.test.js`, `test/journeys/specs/space/index.js` | stopped/destroyed cleanup handling remains unimplemented. | PRESENT | +| `MEET-R-006` | Active/inactive/error/loading UI and legacy call events reflect current meeting/media state and remain keyboard/accessibility compatible. | The visible lifecycle and host callbacks are the consumer contract. | `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.js`, `packages/node_modules/@webex/widget-meet/src/container.js`, `packages/node_modules/@webex/widget-meet/src/events.js` | `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.test.js`, `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/specs/space/guest.js` | direct axe coverage is through Space composition. | PRESENT | + +## Design Overview + +The newer flow separates SDK object ownership from Redux indexing. A thunk asks `sdkInstance.meetings` to create or locate a meeting, the reducer maps destination/locus identifiers to the SDK meeting ID, and selectors retrieve the object/readiness needed by the connected UI. Media-ready events update boolean state; handlers map start/leave controls to thunks. + +The legacy Meet widget instead composes media and Mercury enhancers around its connected view and formats call events for hosts. Membership strings remain exported but are not emitted by `withEventHandler`. Keeping both packages preserves consumers while allowing the newer meetings-plugin model to coexist. + +## Data Flow + +```mermaid +flowchart LR + Host[Destination + credentials] --> Widget[Meet or Meetings widget] + Widget --> Runtime[Shared widget runtime] + Runtime --> SDK[Webex SDK meetings/media/Mercury] + SDK --> Thunks[Meeting/media thunks and event handlers] + Thunks --> Redux[ID indexes + readiness/call state] + Redux --> Selector[Meeting selector] + Selector --> UI[Inactive/Active meeting UI] + UI --> HostEvents[Legacy call events/callbacks] +``` + +Transport is in-process Redux/React plus promise/event-based Webex SDK calls and browser media streams. + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| create/join | Create and join | create/join rejection | +| add media | Media acquisition | missing meeting/media failure | +| leave | Leave lifecycle | lookup/leave rejection | + +```mermaid +sequenceDiagram + participant U as User/Host + participant W as Meetings widget + participant A as Redux thunk + participant S as SDK meetings + participant R as Redux state + U->>W: start meeting + W->>A: createAndJoinMeeting(destination) + A->>S: create(destinationId) + alt create succeeds + A->>R: store meeting ID/indexes + A->>S: bind media events + join() + S-->>R: joined=true + else create/join rejects + S-->>W: rejected promise/error UI + end +``` + +```mermaid +sequenceDiagram + participant W as Active UI + participant A as addMedia thunk + participant S as SDK meeting collection + W->>A: media settings + meetingId + A->>S: locate meeting + alt meeting found + A->>S: getMediaStreams(settings) + S-->>A: local stream/share + A->>S: addMedia(streams, settings) + else missing/rejected + A-->>W: error/rejected operation + end +``` + +```mermaid +sequenceDiagram + participant U as User + participant W as Meeting UI + participant A as leave thunk + participant S as SDK meeting + participant R as Redux state + U->>W: leave + W->>A: destination + A->>S: locate + leave() + alt success + A->>R: joined=false + R-->>W: inactive UI + else lookup/leave failure + A-->>W: error; joined state not falsely cleared + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class ConnectedMeetingsWidget + class MeetingsWidget + class ActiveMeeting + class InactiveMeeting + class MeetingActions + class MeetingsReducer + class SDKMeetingCollection + ConnectedMeetingsWidget *-- MeetingsWidget + MeetingsWidget *-- ActiveMeeting + MeetingsWidget *-- InactiveMeeting + ConnectedMeetingsWidget --> MeetingActions + MeetingActions --> SDKMeetingCollection + MeetingActions --> MeetingsReducer +``` + +## Use Cases + +- **UC-1 Start a destination meeting:** host supplies destination → user starts → SDK creates/joins → UI becomes active and media can attach. +- **UC-2 Join/add media:** active meeting requests configured send/receive streams → SDK returns media → UI renders local/remote media readiness. +- **UC-3 Leave/decline/hang up:** user action invokes SDK lifecycle → state/event/UI transitions to inactive or declined. +- **UC-4 Answer incoming call from Recents:** host receives a call object/room context → mounts Space/Meet flow with that call → lifecycle events report progress. +- **UI flow:** loading/error → inactive call control → active meeting media/leave controls → inactive after leave. +- **Cross-service flow:** Webex meeting/media/Mercury plugins own remote meeting and streams; Redux holds indexes/readiness only. + +## State Model + +- New meetings state: `byDestination`, `byLocusUrl`, and `byId`; each ID maps to `joined`, `hasLocalMedia`, `hasRemoteVideo`, and `hasRemoteAudio`. +- Legacy Meet state combines media/call and widget-specific error/destination details. +- Transitions come from create/store, join, media-ready, leave, incoming call/membership events, and SDK errors. + +## Business Rules & Invariants + +- `buildDestinationLookup` requires both destination ID and type and creates the stable `${type}-${id}` key. +- Never store the SDK meeting object in immutable Redux state; locate it in `meetingCollection.meetings` by ID. +- Record `joined=true/false` only after corresponding SDK promise success. +- Ignore falsey `media:ready` events; set only the readiness flag matching `local`, `remoteVideo`, or `remoteAudio`. + +## Concurrency & Reactive Flow + +- Create/join, media acquisition/addition, and leave are promise chains; consumers must handle rejection. +- Media-ready events can arrive independently and merge into existing state. +- Legacy Mercury/media enhancers listen for call changes; listener teardown and duplicate prevention are mandatory. Exported membership constants do not establish a listener or publisher. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Inactive + Inactive --> Creating: start + Creating --> Joining: meeting created + indexed + Creating --> Error: create rejected + Joining --> Active: join resolved + Joining --> Error: join rejected + Active --> AddingMedia: request streams + AddingMedia --> Active: media added/readiness events + AddingMedia --> Error: media failure + Active --> Leaving: leave + Leaving --> Inactive: leave resolved + Leaving --> Error: leave rejected +``` + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| missing destination/meeting lookup input | thrown lookup error | supply valid destination type/id or meeting ID | +| SDK collection lacks meeting | thrown lookup error | recreate/refresh meeting state; do not fabricate object | +| create/join/media/leave rejection | rejected promise / widget error enhancer | show error and allow retry/leave/remount as appropriate | +| browser media permission/capability absent | media promise failure/no ready event | request permission or run audio-only/compatible browser path | +| meeting destroyed/stopped externally | current implementation gap leaves possible stale state | do not claim full recovery; add handlers/tests before relying on it | + +## Pitfalls + +- `bindMeetingEvents` explicitly lacks media-stopped and meeting stopped/destroyed handlers; stale readiness/index state is a known gap. +- The call object belongs to the SDK and is mutable/non-serializable; keep it outside immutable meeting state and avoid logging/JSON conversion. +- Legacy `widget-meet` membership constants omit the `calls:` prefix used by Space's defined-only call-membership constants. Preserve the exported names, but do not describe either set as emitted until a publisher exists. +- Destination compatibility is shared but implementation paths differ between legacy Meet and newer Meetings. + +## Module Do's / Don'ts + +- DO locate meeting objects through the SDK collection and keep Redux to IDs/readiness. +- DO update UI/status only after SDK lifecycle success and handle promise rejection. +- DON'T merge the two public widget packages or normalize their event strings without a migration plan. + +## Export Stability + +`@ciscospark/widget-meet` was renamed to `@webex/widget-meet`; install/import the current package. Both current widget entrypoints, destination constants, named reducers/actions, and legacy event strings are semver-sensitive. + +## Host Integration & Theming + +Legacy widgets mount through the shared React/browser/data runtime and require host credentials/SDK plus media permissions. Space can embed Meet as an activity; Recents can pass incoming call context. Styles and video/audio elements depend on browser and Momentum UI behavior. + +## Key Design Trade-off + +- Storing only meeting IDs/readiness in Redux favors serializable, immutable state and SDK ownership over easy standalone state inspection; selectors/actions must coordinate with the live SDK collection. + +## Test-Case Strategy (module) + +Existing Jest tests cover active/inactive/aggregate Meetings UI. Space journeys cover pre-call, hang-up before answer, decline, in-call hang-up, event data, guest calling, startup `startCall`, and data API calling. Add direct action/reducer tests for create/join/media/leave, rejection, lookup, media-stopped, destroyed meetings, and listener cleanup. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `MEET-R-001` destination/UI | `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.test.js`, `test/journeys/specs/space/startup-settings.js` | invalid destination unit cases | +| `MEET-R-002` create/join | None found for meeting thunk | blocking test gap before behavioral change | +| `MEET-R-003` ID/readiness model | `packages/node_modules/@webex/redux-module-meetings/src/reducer.js`, `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.test.js` | direct reducer tests | +| `MEET-R-004` add media | `packages/node_modules/@webex/widget-meetings/src/components/ActiveMeeting.test.js` | SDK promise/event unit tests | +| `MEET-R-005` leave | `test/journeys/specs/space/index.js`, `test/journeys/specs/space/guest.js`, `test/journeys/specs/space/data-api.js` | rejected leave and destroyed meeting | +| `MEET-R-006` call UI/events | `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/specs/space/index.js` | direct accessibility/payload tests | + +## Traceability + +- Architecture: `../ARCHITECTURE.md`; registry: `../SPEC_INDEX.md`; contracts/state: `../CONTRACTS.md`, `../SERVICE_STATE.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/recents-spec.md b/ai-docs/modules/recents-spec.md new file mode 100644 index 000000000..3e305b6e0 --- /dev/null +++ b/ai-docs/modules/recents-spec.md @@ -0,0 +1,295 @@ +# Recents — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `recents` | +| Source path(s) | `packages/node_modules/@webex/widget-recents/` | +| Doc kind | Module spec | +| Coverage score | 95% assessed 2026-07-22; entrypoint, options, current events, setup/listener flows, state, failure modes, and journey intent covered | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Current `src/index.js`, public PropTypes/defaults, `src/events.js`, setup/listeners, reducers/selectors, Jest tests, and journey specs are authoritative. Protected guides contribute intent but cannot add an event absent from current constants/listeners. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Existing Recents overview/install/configuration | overview/API/UI | verified/corrected | Supported options, modes, host APIs, teardown, and browser support are placed below. | +| Existing event guide and examples | events | verified/reference-only/stale | Current event set and semantics are in Public Surface/Requirements; payload examples remain native detail; notification examples are stale for this package. | +| Legacy namespace notice | compatibility | verified | Export Stability records the replacement package. | +| Journey test plan | tests | verified | Smoke, data API, global object, events, filters, calls, and accessibility are mapped in Test-Case Strategy. | + +## Overview + +Recents renders the authenticated user's Webex space list and reacts to realtime messages, read state, memberships, and incoming calls. It loads features/teams/spaces/avatars in state-driven stages, supports optional filtering/header controls, and emits selected/add/profile/sign-out/call events to the host. + +The module is a legacy enhanced widget: the package entrypoint composes the shared runtime and intl around a connected container. Setup and listeners translate SDK/Mercury work into shared Redux resource state; UI components render the header, empty state, and spaces list. + +## Purpose / Responsibility + +Own the embeddable recent-spaces list, its initial/realtime loading state, user selection/filter/header interactions, and Recents-specific host events. It does not own remote Webex space data. + +## Stack + +JavaScript, React 16, Redux/React-Redux, Immutable.js, recompose/decorators, react-intl, Webex JS SDK/Mercury, shared components/state modules, Jest, WebdriverIO, and axe-core. + +## Folder / Package Structure + +```text +widget-recents/src/ +├── index.js # public enhanced widget +├── container.js # props, rendering, user/host interactions +├── enhancers/setup.js # initial feature/team/space/avatar loading +├── enhancers/listeners.js # realtime activity/membership handling +├── events.js # current event names and payload builders +├── reducer.js / selector.js # widget-local state and view projection +└── components/ # header, profile, and empty-state UI +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/widget-recents/src/index.js` | public export and shared enhancer composition | +| `packages/node_modules/@webex/widget-recents/src/container.js` | public configuration defaults, rendering, selection/call/profile handlers | +| `packages/node_modules/@webex/widget-recents/src/events.js` | current event strings and payload construction | +| `packages/node_modules/@webex/widget-recents/src/enhancers/setup.js` | authenticated initial-loading workflow and default count | +| `packages/node_modules/@webex/widget-recents/src/enhancers/listeners.js` | realtime message/read/membership processing | +| `packages/node_modules/@webex/widget-recents/src/helpers.js` | space/user/avatar view helpers | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.widget.recents` | SDK/React | default enhanced Recents widget; named reducers | render/load recent spaces | public semver | `packages/node_modules/@webex/widget-recents/src/index.js` | `../CONTRACTS.md` | +| `rw.recents.options` | prop/data API | `basicMode`, header/filter/profile toggles, `spaceLoadCount`; accepted-only `muteNotifications` | host configuration | defaults and data names are public; `muteNotifications` is declared but has no read/use in current Recents source | `packages/node_modules/@webex/widget-recents/src/container.js` | `../CONTRACTS.md` | +| `rw.recents.events` | event | messages, room read/unread/selected, call, membership, add/profile/sign-out | host integration | exact strings stable | `packages/node_modules/@webex/widget-recents/src/events.js` | `../CONTRACTS.md` | + +Compatibility notes: + +- Browser global name is `recentsWidget`; data API toggle is `webex-recents`; teardown uses shared `remove()`. +- Current options default to `basicMode=false`, Add off, filter/profile on, profile menu off, and `spaceLoadCount=25`. +- `muteNotifications` remains an accepted PropTypes compatibility name but is currently unused and has no documented runtime effect. + +## Requires (dependencies) + +- Shared widget runtime/auth/Redux setup and current-user state. +- Spaces, activities, users, teams, media/calls, errors, features, Mercury, avatar, and shared list/loading/error components. +- Webex SDK service and realtime plugins; host credentials/SDK instance; browser DOM/event support. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `RECENTS-R-001` | After SDK auth/registration, Recents obtains feature values, connects Mercury, loads an initial bounded space list, then enriches avatars/teams without duplicate requests. | Staged loading provides responsive encrypted lists and prevents lifecycle re-entry from duplicating work. | `packages/node_modules/@webex/widget-recents/src/enhancers/setup.js` | `packages/node_modules/@webex/widget-recents/src/enhancers/setup.test.js` | Network failure recovery coverage is incomplete. | PRESENT | +| `RECENTS-R-002` | `basicMode=false` uses encrypted conversation loading; `true` uses Webex REST/Hydra and marks the one-stage list complete. | Encryption behavior is security-significant and explicitly controlled by the host. | `packages/node_modules/@webex/widget-recents/src/enhancers/setup.js` | `test/journeys/specs/recents/global/startup-settings.js` | Basic mode removes end-to-end encryption by design. | PRESENT | +| `RECENTS-R-003` | Realtime message/read/membership changes normalize state and emit current host events, excluding own-message unread behavior where implemented. | Host and UI must observe consistent state/event transitions. | `packages/node_modules/@webex/widget-recents/src/enhancers/listeners.js`, `packages/node_modules/@webex/widget-recents/src/events.js` | `test/journeys/specs/recents/global/basic.js` | Full payload snapshots are absent. | PRESENT | +| `RECENTS-R-004` | Selecting a space or call control emits `rooms:selected`, with `action: call` only for call selection; incoming calls emit `calls:created`. | Hosts use these distinctions to open Space/call experiences. | `packages/node_modules/@webex/widget-recents/src/container.js` | `test/journeys/specs/recents/global/basic.js` | Call object serialization is intentionally avoided in logs. | PRESENT | +| `RECENTS-R-005` | Header/filter/profile controls follow their option defaults and emit Add/Profile/Sign-out events only through current handlers. | Optional host UI must be predictable and backward-compatible. | `packages/node_modules/@webex/widget-recents/src/container.js` | `packages/node_modules/@webex/widget-recents/src/components/RecentsHeader.test.js`, `test/journeys/specs/recents/global/space-list-filter.js` | Sign-out side effect is host-owned; widget emits intent. | PRESENT | +| `RECENTS-R-006` | Ready/loading/empty/error rendering derives from fetch/filter/status state and remains accessible. | Users need deterministic feedback and the test plan requires axe-clean behavior. | `packages/node_modules/@webex/widget-recents/src/container.js` | Recents smoke/data/global journeys | Some error variants lack journey coverage. | PRESENT | + +## Design Overview + +Setup is a guarded state machine keyed by SDK and widget-status flags. It fetches feature settings, connects the realtime channel, loads encrypted or basic-mode spaces, enriches direct-space users/avatars, and fetches teams. Realtime listeners then update cached space/activity/membership state and invoke the container's event callback. + +The connected container deliberately rerenders only for relevant list/error/widget/call references. It keeps host interaction logic—selection, call selection, Add/Profile/Sign-out, filter, and scrolling—close to the rendered list, while SDK work remains in thunks/enhancers. + +## Data Flow + +```mermaid +flowchart LR + Host[Options + credentials] --> Runtime[Shared widget runtime] + Runtime --> Setup[Recents setup] + Setup --> SDK[Webex SDK REST/encrypted APIs + Mercury] + SDK --> Redux[spaces/users/teams/features/media/widget state] + Redux --> Selector[Recents selector] + Selector --> View[Header + SpacesList + empty/error UI] + SDK -. realtime .-> Listeners[Recents listeners] + Listeners --> Redux + View --> Events[Host event callback/DOM dispatch] +``` + +Remote transport is Webex SDK promises and Mercury events; internal transport is Redux actions/selectors and React props; outward transport is callback/CustomEvent/ampersand events. + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| initial load | Authenticated list setup | waits, encrypted/basic alternatives, failure status | +| realtime update | Message/read/membership processing | missing cached space/fetch alternative | +| user selection/header action | Host intent event | option/unknown target branches | + +```mermaid +sequenceDiagram + participant R as Recents setup + participant S as Webex SDK + participant X as Redux + alt SDK not ready + R-->>R: wait without fetching + else ready + R->>S: fetch feature flags + teams + R->>S: connect Mercury + alt basicMode + R->>S: fetch REST spaces(max) + else encrypted mode + R->>S: fetch encrypted conversations(max) + S-->>X: incremental decrypted spaces + R->>S: fetch avatars + end + S-->>X: mark initial/all spaces loaded + end +``` + +```mermaid +sequenceDiagram + participant M as Mercury + participant L as Recents listener + participant X as Redux + participant H as Host + M->>L: activity or membership event + alt cached space exists + L->>X: update space/activity/read/membership state + else target must be fetched + L->>X: fetch/store related space + end + L-->>H: current event name + constructed detail +``` + +```mermaid +sequenceDiagram + participant U as User + participant C as Recents container + participant H as Host + U->>C: select space/call/add/profile/sign-out + alt control enabled and target known + C-->>H: rooms:selected or option-specific event + else optional control hidden/disabled + C-->>U: no action surface + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class RecentsWidget + class SetupEnhancer + class ListenerEnhancer + class RecentsSelector + class RecentsReducer + class RecentsHeader + class SpacesList + RecentsWidget --> SetupEnhancer + RecentsWidget --> ListenerEnhancer + RecentsReducer --> RecentsSelector + RecentsSelector --> RecentsWidget + RecentsWidget *-- RecentsHeader + RecentsWidget *-- SpacesList +``` + +## Use Cases + +- **UC-1 Browse recents:** authenticated user opens widget → initial spaces load → avatars/teams/features enrich → list/empty state appears. Evidence: setup code and Recents journeys. +- **UC-2 React to incoming activity:** Webex emits message/read/membership change → listeners normalize space/activity → list/unread state changes → host receives supported event. +- **UC-3 Select/open/call a space:** user selects row or call control → `rooms:selected` payload identifies target/action → host opens its chosen experience. +- **UC-4 Filter/manage header:** user filters list or clicks configured Add/Profile/Sign-out control → local view/event updates without inventing service-side behavior. +- **UI flow:** loading → list or empty/error; optional header contains filter/Add/profile/menu; list supports unread and call indicators. +- **Cross-service flow:** Webex REST/encrypted conversation APIs provide the list; Mercury drives updates; people/team/feature/avatar SDK operations enrich display data. + +## State Model + +- Widget-local state tracks initial/all-space, avatar/team/feature fetch flags, scroll position, and keyword filter. +- Shared state tracks spaces by ID/list, users, teams, activities, media/incoming calls, errors, SDK/Mercury status, and features. +- Status flags are guards: a fetch/connect starts only when its `is*`/`has*` state permits. + +## Business Rules & Invariants + +- `spaceLoadCount <= 0` falls back to 25. Enforced in setup. +- Direct-space titles/avatars need the participant other than the current user; store/fetch that user before final display. +- Basic mode is an explicit opt-in and cannot be presented as encrypted. +- `rooms:selected` call payload includes `action: call`; ordinary selection does not. + +## Concurrency & Reactive Flow + +- Initial load, decryption, avatar fetches, team/feature fetches, and Mercury events run asynchronously; status flags prevent duplicate starts. +- Encrypted spaces may resolve incrementally; `Promise.all` tracks avatar enrichment without blocking initial state semantics beyond implemented stages. +- Realtime activity can precede cached space availability; listeners fetch/construct the missing context before normal processing where supported. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> WaitingForSDK + WaitingForSDK --> ConnectingRealtime: authenticated + registered + ConnectingRealtime --> LoadingSpaces: Mercury connected + LoadingSpaces --> LoadingAvatars: encrypted mode + spaces ready + LoadingSpaces --> Ready: basic mode complete + LoadingAvatars --> Ready + Ready --> Ready: realtime update/filter/scroll + LoadingSpaces --> Error: SDK rejection + LoadingAvatars --> Error: unrecovered failure + Error --> LoadingSpaces: retry/remount +``` + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| auth/registration missing | not-ready/loading | provide valid token/SDK and await registration | +| space/feature/team/avatar request fails | rejected thunk/status/error store | retry/remount; inspect SDK logs; keep already loaded state where implemented | +| no spaces or filter has no matches | intentional empty state | change filter or use Add control if enabled | +| incoming call lacks conversation URL | fallback call ID used as space identity | host handles limited context in `calls:created` | +| event payload contains call object | call omitted from logger but still delivered to callback | do not stringify/log the raw call object | + +## Pitfalls + +- Protected event docs list `notifications:created/clicked`, but current Recents constants do not; they are not current contracts. +- A source comment says pagination is to come; current encrypted/basic loading marks all spaces fetched after its one implemented stage. +- `handleProfileClick` emits `currentUserWithAvatar`, not every injected user-shaped prop. +- Event docs may show older payload fields/examples; current constructors are authoritative. +- `basicMode` changes the security properties of loading and must never be enabled as a neutral performance tweak. + +## Module Do's / Don'ts + +- DO guard async setup with widget-status flags and route host events through `handleEvent`/current constructors. +- DON'T fetch lists directly from the view, duplicate Mercury listeners, or document legacy notification events as current. + +## Export Stability + +`@ciscospark/widget-recents` was renamed to `@webex/widget-recents`; install/import the current namespace. Preserve the default package entrypoint, `recentsWidget`, `webex-recents`, option defaults, and current event strings through normal semver/deprecation rules. + +## Host Integration & Theming + +Hosts import the React package or mount through `window.webex.widget(element).recentsWidget(options)` / `data-toggle="webex-recents"`. CDN/imported styles are required. Hosts own follow-up behavior for selected/add/profile/sign-out events and must supply compatible credentials/SDK. + +## Key Design Trade-off + +- Encrypted mode favors security and incremental decryption/avatars over the simpler REST list; `basicMode` preserves a simpler integration at the explicit cost of end-to-end encryption. + +## Test-Case Strategy (module) + +Jest covers helpers, setup, header/profile/empty components. WebdriverIO covers data API and global instantiation, group/direct updates, unread/read, selection/call buttons, memberships, incoming calls, filter/startup settings, multiple widgets, demo auth forms, and axe accessibility. Add negative cases for rejected SDK operations and listener duplication. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `RECENTS-R-001` staged setup | `packages/node_modules/@webex/widget-recents/src/enhancers/setup.test.js` | failure/retry permutations | +| `RECENTS-R-002` basic/encrypted mode | `test/journeys/specs/recents/global/startup-settings.js` | explicit encryption assertion | +| `RECENTS-R-003` realtime/events | `test/journeys/specs/recents/global/basic.js` | full payload snapshots and stale-event negative test | +| `RECENTS-R-004` selection/call | `test/journeys/specs/recents/global/basic.js`, `test/journeys/specs/recents/dataApi/basic.js` | unknown/missing target negative case | +| `RECENTS-R-005` header/filter | `packages/node_modules/@webex/widget-recents/src/components/RecentsHeader.test.js`, `test/journeys/specs/recents/global/space-list-filter.js` | sign-out host effect remains external | +| `RECENTS-R-006` ready/error/a11y | `test/journeys/specs/smoke/widget-recents/index.js`, `test/journeys/specs/recents/global/basic.js` | more error variants | + +## Traceability + +- Repo architecture: `../ARCHITECTURE.md`; registry: `../SPEC_INDEX.md`; contracts/state: `../CONTRACTS.md`, `../SERVICE_STATE.md`. +- Coverage state, source routing, profile, and contract baseline: `.sdd/manifest.json`. diff --git a/ai-docs/modules/shared-ui-components-spec.md b/ai-docs/modules/shared-ui-components-spec.md new file mode 100644 index 000000000..88dd64056 --- /dev/null +++ b/ai-docs/modules/shared-ui-components-spec.md @@ -0,0 +1,224 @@ +# Shared UI Components — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `shared-ui-components` | +| Source path(s) | `packages/node_modules/@webex/react-component-*/`, `private-react-component-*/` | +| Doc kind | Module spec | +| Coverage score | 93% assessed 2026-07-22; all entrypoints and component families covered, with direct behavior detail concentrated on high-coupling components | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Package entrypoints, PropTypes/TypeScript types, styles, stories, and adjacent tests define behavior. A story demonstrates a supported rendering state but is not proof of error handling. Protected legacy rename notices establish namespace history only. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Legacy `@ciscospark/react-component-*` READMEs | namespace migration | verified | Current `@webex` entrypoints and compatibility policy are in Export Stability. | +| Component source and package metadata | exports, props, rendering | authoritative | Public Surface, Requirements, Design Overview. | +| Adjacent Jest tests and Storybook stories | behavior examples | verified | Test-Case Strategy. | + +## Overview + +This capability is the visual vocabulary used by widgets and containers: activity renderers, buttons, media elements, avatars, file affordances, adaptive-card inputs, separators, loading/error views, and utility functions. Most packages export one presentational React component; a few deliberately connect to Redux or browser APIs. + +## Purpose / Responsibility + +Provide reusable, composable UI primitives with stable package entrypoints and explicit props. Components must not silently take ownership of widget-level authentication, routing, or remote-resource lifecycle. + +## Stack + +JavaScript/TypeScript, React 16, PropTypes, CSS modules/SCSS, Momentum UI, react-intl, Immutable.js where supplied by callers, Jest, react-test-renderer, and Storybook. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── react-component-*/src/ # public components and utilities +└── private-react-component-*/src/ # repository-internal examples/helpers +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/react-component-activity-item/src/index.js` and the exact package paths indexed in `../CONTRACTS.md` | package public entrypoints and primary components | +| `packages/node_modules/@webex/react-component-utils/src/index.js` | public utility barrel | +| `packages/node_modules/@webex/react-component-adaptive-card/src/index.js` | adaptive-card DOM/Redux integration and cleanup | +| `packages/node_modules/@webex/react-component-activity-item/src/index.js` | post/share/system-message dispatch | +| `packages/node_modules/@webex/react-component-audio/src/index.js` and `packages/node_modules/@webex/react-component-video/src/index.js` | MediaStream-to-element binding | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.ui.components` | SDK/React | 54 `@webex/react-component-*` package entrypoints | independently reusable widget presentation | public semver; default/named exports and required props are stable | exact paths in the 54 `rw.ui.*` catalog rows; representative: `packages/node_modules/@webex/react-component-activity-item/src/index.js`, `packages/node_modules/@webex/react-component-video/src/index.js` | `../CONTRACTS.md` | +| `rw.ui.utils` | SDK | `@webex/react-component-utils` named barrel | shared files/components/date/activity/UUID/validation/adaptive-card helpers | public semver; removal/rename is breaking | `packages/node_modules/@webex/react-component-utils/src/index.js` | `../CONTRACTS.md` | + +Compatibility notes: + +The 54 non-private packages are public entrypoints. Most default-export a component named by the package; exact props and named exports remain source-defined at each `src/index.*`. + +High-coupling contracts include: + +- `ActivityItem` selects post, share, ECM-link, or system-message presentation from activity verb/content. +- `AdaptiveCard` renders SDK-backed card content, submits actions, replaces decrypted image URLs, and unmounts child React roots. +- `Audio` and `Video` assign a supplied `MediaStream` to `srcObject`; `Video` supports local-audio muting. +- `ButtonControls` chooses Momentum `CallControl` versus `ActivityButton` from each descriptor. + +## Requires (dependencies) + +- React/ReactDOM and host DOM/media support. +- Momentum UI, react-intl, CSS-module processing, and package-specific utilities. +- Selected components require Redux state/actions, the Webex SDK, or container packages; these dependencies must remain visible in their entrypoints. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `UI-R-001` | Each public component package preserves its default/named entrypoint and declared prop contract. | Widgets and external consumers import packages independently. | `packages/node_modules/@webex/react-component-activity-item/src/index.js`, `packages/node_modules/@webex/react-component-utils/src/index.js`, `ai-docs/CONTRACTS.md` | `packages/node_modules/@webex/react-component-activity-item/src/index.test.js` | Not every prop combination has a test. | PRESENT | +| `UI-R-002` | Activity rendering selects the component appropriate to verb/content and leaves unknown verbs empty. | Conversation feeds must not misrepresent activity types. | `packages/node_modules/@webex/react-component-activity-item/src/index.js` | `packages/node_modules/@webex/react-component-activity-item/src/index.test.js` | ECM/adaptive-card remote payloads depend on SDK shape. | PRESENT | +| `UI-R-003` | Stream components return no element without a stream and bind valid streams through element refs. | Avoid invalid playback nodes and keep media ownership with the caller. | `packages/node_modules/@webex/react-component-audio/src/index.js`, `packages/node_modules/@webex/react-component-video/src/index.js` | `packages/node_modules/@webex/react-component-audio/src/index.test.js` | Video has no adjacent Jest test; browser autoplay policy is external. | PRESENT | +| `UI-R-004` | Adaptive-card child roots are unmounted and transient submission status is dismissed. | Prevent leaked subtrees and stale status UI. | `packages/node_modules/@webex/react-component-adaptive-card/src/index.js` | `packages/node_modules/@webex/react-component-adaptive-card/src/index.test.js` | A two-second timer remains implementation-defined. | PRESENT | +| `UI-R-005` | Interactive controls expose labels/ARIA data and invoke supplied callbacks without owning business operations. | Accessibility and composability depend on caller-controlled actions. | `packages/node_modules/@webex/react-component-button-controls/src/index.js`, `packages/node_modules/@webex/react-component-incoming-call/src/index.js` | `packages/node_modules/@webex/react-component-button-controls/src/index.test.js`, `test/journeys/specs/smoke/widget-space/index.js` | Repository-wide journey coverage is uneven. | PRESENT | + +## Design Overview + +Components receive normalized data and callbacks from containers/widgets. Pure functions and small functional components dominate; class components are used where DOM refs or lifecycle cleanup are required. CSS modules isolate styles while stable `webex-*` class names support host inspection and existing tests. + +## Data Flow + +```mermaid +flowchart LR + Widget[Widget/container] --> Props[Props and callbacks] + Redux[Selected Redux state] --> Props + Props --> Component[Shared component] + Component --> DOM[Accessible DOM/media] + Component --> Callback[Caller callback/action] + SDK[SDK helper/data] --> Component +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| render, interact, update, dispose | Component lifecycle | absent data, callback ownership, and child-root cleanup share this actor/order group | + +```mermaid +sequenceDiagram + participant P as Parent + participant C as Component + participant D as DOM + P->>C: props/data/callbacks + C->>D: render accessible view + D->>C: user interaction + C-->>P: callback with component-owned value + opt component owns child DOM roots + P->>C: unmount + C->>D: detach/unmount children + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class WidgetOrContainer + class PresentationalComponent + class ComponentUtils + class MomentumUI + class ReduxConnectedComponent + WidgetOrContainer --> PresentationalComponent + PresentationalComponent --> ComponentUtils + PresentationalComponent --> MomentumUI + ReduxConnectedComponent --|> PresentationalComponent +``` + +## Use Cases + +- Render conversation activities using the activity verb, content, files, actors, and timestamps. +- Bind remote/local media streams to browser media elements. +- Compose labeled call or activity controls from descriptors. +- Render and submit adaptive cards while displaying sending/success/failure state. +- Reuse loading, error, avatar, badge, list, input, and separator primitives across widget packages. + +## State Model + +Most functional components are stateless projections. Stateful class components such as AdaptiveCard own only transient UI/DOM state: child roots, decrypted-image replacement readiness, and submission status. Parent widgets/containers continue to own domain and remote-resource state. + +## Business Rules & Invariants + +- Required props remain required at the component boundary; defaults cover only optional presentation. +- A component that receives a callback invokes it but does not duplicate the owning Redux/SDK operation. +- DOM-root, timer, listener, and object-URL resources created by a component require lifecycle cleanup. +- CSS and accessibility attributes are part of observable UI behavior even when they are not JavaScript exports. + +## Concurrency & Reactive Flow + +React prop/state updates may race SDK or image decryption results. Adaptive cards wait until all decrypted URLs are available before replacing rendered card content; asynchronous status changes must not update an unmounted subtree. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Empty + Empty --> Content: valid props/data + Content --> Active: interaction or async operation + Active --> Content: success/dismiss + Active --> ErrorView: component-supported failure state + Content --> Disposed: unmount + ErrorView --> Disposed: unmount +``` + +## Pitfalls + +- Some “component” packages connect Redux and are not pure. +- Stories are examples, not exhaustive specifications. +- Do not remove stable CSS classes merely because CSS modules generate local names. +- Adaptive-card failures use the SDK logger; callers must own broader recovery. +- Media autoplay, codec, and permission behavior remains browser-owned. + +## Module Do's / Don'ts + +- Do keep public entrypoints narrow and test behavior close to the component. +- Do clean resources created in lifecycle methods. +- Don't import widget orchestration into shared presentation. +- Don't change a named/default export or required prop without semver review. + +## Export Stability + +Public `@webex/react-component-*` entrypoints are semver contracts. The protected `@ciscospark/react-component-*` READMEs document that each legacy package moved to the identically suffixed `@webex` namespace; they are migration evidence, not additional runtime exports. Private-prefixed packages remain internal. + +## Host Integration & Theming + +Components inherit host font/style setup through the widget bundle, Momentum UI, CSS modules, and stable class hooks. Callers provide locale/intl, callbacks, SDK-backed values, and media streams. Avoid global styling beyond the existing font/theme entrypoints. + +## Key Design Trade-off + +The many small packages improve independent reuse and tree selection, but multiply public entrypoints and upgrade obligations. Preserve package boundaries unless a migration plan accounts for every consumer. + +## Test-Case Strategy (module) + +| Requirement | Current evidence | Focused gap | +|---|---|---| +| `UI-R-001` entrypoints/props | `packages/node_modules/@webex/react-component-activity-item/src/index.test.js`, `ai-docs/CONTRACTS.md` | automated export inventory | +| `UI-R-002` activity selection | `packages/node_modules/@webex/react-component-activity-item/src/index.test.js` | malformed/unknown activity matrix | +| `UI-R-003` stream binding | `packages/node_modules/@webex/react-component-audio/src/index.test.js`; no Video test found | browser autoplay rejection | +| `UI-R-004` cleanup/status | `packages/node_modules/@webex/react-component-adaptive-card/src/index.test.js` | fake-timer/unmount race | +| `UI-R-005` accessibility/callbacks | `packages/node_modules/@webex/react-component-button-controls/src/index.test.js`, `test/journeys/specs/smoke/widget-space/index.js` | keyboard matrix per composite | + +## Traceability + +- Architecture and package inventory: `../ARCHITECTURE.md`, `../CONTRACTS.md`. +- Coding conventions: `../patterns/react-component-entrypoint.md`, `../rules/preserve-public-entrypoints.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/space-messaging-spec.md b/ai-docs/modules/space-messaging-spec.md new file mode 100644 index 000000000..f6f68870b --- /dev/null +++ b/ai-docs/modules/space-messaging-spec.md @@ -0,0 +1,326 @@ +# Space and Messaging — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). This is the canonical capability spec for Space, Message, Files, and Roster packages. + +## Metadata + +| Field | Value | +|---|---| +| Module id | `space-messaging` | +| Source path(s) | `packages/node_modules/@webex/widget-space/`, `widget-message/`, `widget-files/`, `widget-roster/` | +| Doc kind | Module spec | +| Coverage score | 94% assessed 2026-07-22; all package entrypoints, current events, major flows, state, failures, and journey intent covered; sparse unit coverage remains for composed widget flows | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Requirements cite stable implementation and test paths only. Protected usage/event documents supplied reconciled intent; code constants, props, reducers, and tests decide conflicts. Missing tests are gaps, not inferred passes. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Existing widget usage/configuration guidance | overview, host API, UI | verified/corrected | Current options and destination behavior are in Public Surface, Requirements, UI Flow, and Host Integration. | +| Existing event guide and payload examples | events | verified/reference-only/stale | Current constant set is in Public Surface; detailed payload construction stays in code; unsupported notification/mention examples are flagged in Pitfalls. | +| Legacy package rename notices | compatibility | verified | Namespace mapping is in Export Stability; legal notices remain in their protected sources. | +| Journey test plan | tests | verified | Space smoke, primary, messaging, file, meet, guest, settings, data-API, and accessibility intent is mapped in Test-Case Strategy. | + +## Overview + +This capability presents a Webex space as an activity-oriented widget. `widget-space` selects a destination and composes message, meeting, file, and roster activities. The focused child widgets remain importable separately, while the full widget supplies navigation, destination setup, Redux state, SDK integration, error/loading UI, and host events. + +Maintainers should start at each package `src/index.js`, then follow Space `container.js`, `enhancers/setup.js`, reducer/selector, and the child widget entrypoint. Reusable rendering/state behavior belongs in Shared UI, Containers/HOCs, or Redux modules rather than being duplicated here. + +## Purpose / Responsibility + +Own the embeddable space experience: resolve a typed Webex destination, expose enabled activities, coordinate messaging/files/roster/meeting presentation, and report current host events. It does not own Webex remote data or SDK transport. + +## Stack + +JavaScript, React 16, PropTypes, Redux/React-Redux, Immutable.js, recompose, react-intl, Momentum UI, Webex JS SDK plugins, Jest, and WebdriverIO journeys; Babel/Rollup/Webpack build the package outputs. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── widget-space/src/ # full activity shell, setup, state, selectors, events +├── widget-message/src/ # conversation message composition/display +├── widget-files/src/ # files activity composition +└── widget-roster/src/ # people/roster activity composition +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/widget-space/src/index.js` | exported widget, reducers, destination types, event names, enhancer composition | +| `packages/node_modules/@webex/widget-space/src/container.js` | public props, defaults, activity UI, keyboard behavior, unregistration | +| `packages/node_modules/@webex/widget-space/src/constants.js` | activity and destination constants | +| `packages/node_modules/@webex/widget-space/src/events.js` | current host event strings | +| `packages/node_modules/@webex/widget-space/src/enhancers/setup.js` | authenticated setup, destination normalization, reload, Mercury, data fetch | +| child widget `src/index.js` files | child package exports and enhancer composition | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.widget.space` | SDK/React | default `SpaceWidget`; named `eventNames`, `reducers`, `destinationTypes` | full space experience | public semver; preserve props/events/destination strings | `packages/node_modules/@webex/widget-space/src/index.js` | `../CONTRACTS.md` | +| `rw.widget.message` | SDK/React | default Message widget; named reducers/destination types | focused messaging activity | public semver | `packages/node_modules/@webex/widget-message/src/index.js` | `../CONTRACTS.md` | +| `rw.widget.files` | SDK/React | default Files widget | focused file activity | public semver | `packages/node_modules/@webex/widget-files/src/index.js` | `../CONTRACTS.md` | +| `rw.widget.roster` | SDK/React | default Roster widget; named reducers | focused people/roster activity | public semver | `packages/node_modules/@webex/widget-roster/src/index.js` | `../CONTRACTS.md` | +| `rw.space.destinations` | SDK/prop | `email`, `userId`, `spaceId`, `sip`, `pstn` where accepted | identify target conversation/call | exact strings stable | `packages/node_modules/@webex/widget-space/src/constants.js` | `../CONTRACTS.md` | +| `rw.space.events` | event | emitted `messages:created`, `rooms:read`, `rooms:unread`, `calls:created/connected/disconnected`, and `activity:changed` | notify host of current Space/child behavior | exact emitted strings stable; payload additive only | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js` | `../CONTRACTS.md` | +| `rw.widget.space` | SDK/prop | `initialActivity` | choose the valid primary activity at startup | accepted prop/data attribute; defaults to `message` in the activity enhancer | `packages/node_modules/@webex/widget-space/src/enhancers/activity-menu.js` | `../CONTRACTS.md` | +| `rw.widget.space` | SDK/prop | `setCurrentActivity` | request a valid primary or secondary activity after mount | accepted prop; changes emit `activity:changed` | `packages/node_modules/@webex/widget-space/src/container.js`, `packages/node_modules/@webex/widget-space/src/enhancers/external-control.js` | `../CONTRACTS.md` | + +Compatibility notes: + +- Imported React, browser-global, and data-attribute forms remain supported through the shared runtime. +- Current `eventNames` is authoritative. Older event examples not present there are not active contracts. +- The exported `calls:memberships:*` constants have no publisher in current Space/Meet/Message source and are definition-only, not active emitted contracts. + +## Requires (dependencies) + +- `@webex/webex-widget-base` for Redux/SDK/auth/host integration and teardown. +- Message/files/roster/meet child widgets, shared components, containers, and Redux resource modules. +- Webex SDK authentication, device, conversation, rooms/people/meetings, Mercury, presence, search, team, feature, and flag plugins. +- Host credentials or a compatible authenticated SDK instance; browser DOM/media capabilities for UI/calling. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `SPACE-R-001` | The full widget accepts a supported destination type/id and resolves/stores the target only after SDK authentication and registration. | Prevents network/setup work against an unauthenticated SDK and keeps destination changes deterministic. | `packages/node_modules/@webex/widget-space/src/enhancers/setup.js` | `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js`, `test/journeys/specs/space/startup-settings.js` | PSTN/SIP behavior depends on SDK capability. | PRESENT | +| `SPACE-R-002` | Enabled `spaceActivities` determine message, meet, files, and people UI; disabling the initial activity produces an error rather than silently selecting an invalid activity. | Host configuration must predict visible navigation and startup state. | `packages/node_modules/@webex/widget-space/src/container.js`, `packages/node_modules/@webex/widget-space/src/enhancers/activity-menu.js` | `test/journeys/specs/space/startup-settings.js` | Some composition paths lack direct Jest tests. | PRESENT | +| `SPACE-R-003` | Message behavior supports sending/receiving, markdown, attachments, flags, deletion rules, mentions, and composer option controls through existing child/state packages. | These are the core promised space interactions and must remain compatible. | `packages/node_modules/@webex/widget-message/src/actions.js`, `packages/node_modules/@webex/widget-message/src/container.js`, `packages/node_modules/@webex/redux-module-activity/src/actions.js` | `test/journeys/specs/space/index.js`, `test/journeys/lib/test-helpers/space-widget/messaging.js` | Journey credentials/services required. | PRESENT | +| `SPACE-R-004` | The widget emits only event constants that have an observed publisher through host callbacks/browser event translation; defined-only `calls:memberships:*` constants are not claimed as emitted. | Constants alone do not prove a host event, and consumers need the implemented event boundary. | `packages/node_modules/@webex/widget-space/src/events.js`, `packages/node_modules/@webex/widget-space/src/enhancers/activity-menu.js`, `packages/node_modules/@webex/widget-space/src/enhancers/external-control.js`, `packages/node_modules/@webex/widget-meet/src/enhancers/withEventHandler.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | `test/journeys/lib/events.js`, `test/journeys/specs/space/index.js` | Payload examples in old docs may omit current fields; membership constants have no publisher. | PRESENT | +| `SPACE-R-005` | Keyboard navigation preserves tab roles, arrow/Home/End behavior, Meet-button focus handoff, and accessible labels. | The widget is customer-facing UI and journey plans require no accessibility violations. | `packages/node_modules/@webex/widget-space/src/container.js` | `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/specs/space/index.js` | Axe journeys are environment-dependent. | PRESENT | +| `SPACE-R-006` | Unmount/removal unregisters device/widget state through established teardown paths. | Prevents leaked registrations, listeners, stores, and duplicate host events. | `packages/node_modules/@webex/widget-space/src/container.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withRemoveWidget.js` | `packages/node_modules/@webex/widget-space/src/reducer.test.js` | End-to-end repeated mount/remove coverage is limited. | WEAK | + +## Design Overview + +The package exports a connected container wrapped by the shared widget enhancer and intl. `setup` is the state-driven coordinator: after SDK readiness it normalizes destinations, connects Mercury, fetches space/user/avatar data, and requests reload when destination changes. The container derives visible activity widgets, renders loading/error/content states, and forwards user/external-control activity changes. + +Child widgets encapsulate focused activity composition. Redux modules normalize remote resources and async status so views remain mostly declarative. This division avoids placing SDK state machines inside presentational components and lets the same Message/Files/Roster packages be reused outside the full Space shell. + +## Data Flow + +```mermaid +flowchart LR + Host[Host props/data attributes] --> Base[webex-widget-base] + Base --> Store[Widget Redux store] + Store --> Setup[Space setup enhancer] + Setup --> SDK[Webex SDK + Mercury] + SDK --> Modules[conversation/activity/space/user/media reducers] + Modules --> Selector[Space selector] + Selector --> Shell[Space container] + Shell --> Children[Message / Files / Roster / Meet] + Children --> SDK + Shell --> HostEvents[Host callback and DOM events] +``` + +Transport is in-process React/Redux for composition, promise-based Webex SDK calls for remote operations, and SDK/Mercury events for realtime updates. + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| initialize/change destination | Destination setup | waits for auth/registration; reload/error branches | +| send/receive content | Messaging/file flow | SDK rejection and error-state branch | +| switch/start activity | Activity flow | disabled/unavailable activity branch | + +```mermaid +sequenceDiagram + participant H as Host + participant S as Space setup + participant R as Redux modules + participant W as Webex SDK + H->>S: destinationType + destinationId + alt SDK not authenticated/registered + S-->>H: loading; no fetch + else ready + S->>R: store normalized destination + S->>W: connect Mercury and fetch target data + W-->>R: conversation/space/user/activity data + R-->>H: selector renders activities + end + opt destination changes + S->>R: reset conversation/errors and reload + end +``` + +```mermaid +sequenceDiagram + participant U as User + participant M as Message/File UI + participant R as Redux thunk + participant W as Webex SDK + participant H as Host + U->>M: send message or attachment + M->>R: dispatch operation + R->>W: SDK conversation/file call + alt success/realtime echo + W-->>R: normalized activity + R-->>M: rendered activity + M-->>H: messages:created when applicable + else rejection/decrypt/download failure + W-->>R: reject/error action + R-->>M: error display/retry affordance + end +``` + +```mermaid +sequenceDiagram + participant U as User/Host + participant A as Activity menu/control + participant C as Space container + participant H as Host callback + U->>A: select/set activity + alt activity enabled and available + A->>C: update primary/secondary activity + C-->>U: render selected child widget + C-->>H: activity:changed + else disabled or meeting unavailable + C-->>U: preserve current activity or render error/disabled state + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class SpaceWidget + class SetupEnhancer + class SpaceReducer + class SpaceSelector + class MessageWidget + class FilesWidget + class RosterWidget + class MeetWidget + SpaceWidget --> SetupEnhancer + SetupEnhancer --> SpaceReducer + SpaceReducer --> SpaceSelector + SpaceSelector --> SpaceWidget + SpaceWidget *-- MessageWidget + SpaceWidget *-- FilesWidget + SpaceWidget *-- RosterWidget + SpaceWidget *-- MeetWidget +``` + +The full widget owns orchestration and navigation; child widgets own activity presentation/operations; shared Redux modules own client representations and async transitions. + +## Use Cases + +- **UC-1 Open a space:** host supplies `spaceId`, email, or user ID → setup authenticates/resolves destination → enabled activities render. Evidence: `packages/node_modules/@webex/widget-space/src/enhancers/setup.js`, `test/journeys/specs/space/startup-settings.js`. +- **UC-2 Message and share files:** user composes text/markdown/attachment → Redux/SDK sends → realtime state renders the result and host events. Evidence: `test/journeys/lib/test-helpers/space-widget/messaging.js`. +- **UC-3 Inspect/manage participants:** user opens People → roster lists/counts/searches/adds participants → close returns to the primary activity. Evidence: `test/journeys/lib/test-helpers/space-widget/roster.js`. +- **UC-4 Start/answer a call:** user selects Meet or host passes call/start settings → call UI invokes SDK behavior → lifecycle events/status update. Evidence: `test/journeys/lib/test-helpers/space-widget/meet.js`. +- **UI flow:** loading/error → activity shell → message/files/people tabs plus Meet control → secondary activity overlays according to `secondaryActivitiesFullWidth`; keyboard navigation follows the tab/Meet rules. +- **Cross-service flow:** all remote data and call operations pass through supplied Webex SDK plugins; Mercury supplies realtime conversation/activity updates. + +## State Model + +- Widget state tracks destination, primary/secondary activity, reload/fetch status, and configuration-derived activity types. +- Combined reducers add conversation, activities, spaces, users, errors, media/calls, Mercury, presence, flags/features, and child-widget state. +- Triggers include host prop changes, setup lifecycle, SDK promise resolution/rejection, realtime events, user navigation, and teardown. + +## Business Rules & Invariants + +- An initial activity must be enabled; otherwise surface an error. Enforced in Space activity/setup logic. +- Destination strings and IDs must be normalized before fetch; email is lowercased and Hydra space IDs are decoded/cluster-aware. Enforced in `packages/node_modules/@webex/widget-space/src/enhancers/setup.js`. +- Meet is disabled for a space destination when the SDK has no preferred Webex site. Enforced in `packages/node_modules/@webex/widget-space/src/container.js`. +- Users may delete their own messages but not another person's; journey intent preserves this authorization-facing UI rule. + +## Concurrency & Reactive Flow + +- React lifecycle/setup runs repeatedly as SDK and destination props change; status flags prevent duplicate fetch/connect operations. +- Mercury activities and SDK call/media events asynchronously update Redux and may emit host events; listeners must not be registered twice. +- Destination changes reset existing conversation/errors before loading the replacement; do not let stale async results become the visible target. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> WaitingForSDK + WaitingForSDK --> ResolvingDestination: authenticated + registered + ResolvingDestination --> LoadingSpace: destination stored + LoadingSpace --> Ready: target data available + LoadingSpace --> Error: SDK/setup failure + Ready --> Reloading: destination/config requests reload + Reloading --> ResolvingDestination + Error --> Reloading: recoverable retry/change + Ready --> Removed: host remove/unmount + Error --> Removed +``` + +## UI Flow + +- Loading state precedes available target data; persistent/temporary errors render through `ErrorDisplay`. +- Message, Files, and People appear as tabs when enabled; Meet is a separate call control and can show active-call time. +- Empty/disabled initial activity, unavailable meeting site, incoming call, secondary activity, and teardown are non-happy paths that must remain visible/tested. + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| missing/invalid destination | setup cannot resolve/fetch; error state/UI | provide a supported type/id and rerender | +| SDK unauthenticated/unregistered | widget remains loading/not ready | supply valid credentials/SDK and allow registration | +| conversation/resource operation fails | Redux error/conversation error displayed | retry/change destination; inspect SDK logger | +| disabled initial activity | explicit widget error | enable the activity or select an enabled initial value | +| meeting unavailable for space | disabled Meet/error title | configure preferred Webex site or use another activity | +| file decrypt/download fails | promise/error state from file modules | retry when SDK/network is available | + +## Pitfalls + +- Existing protected event guides list `notifications:*` and `mention:clicked`, but the current Space event constants do not; do not expose them without an intentional code/spec change. +- `secondaryActivitiesFullWidth` defaults to `true` in current code even though older usage text described `false`; code is authoritative. +- `spaceActivities` is the current prop; older example code may use `activities`. +- The activity-menu/Meet focus order is hand-coded; DOM/class/role changes can regress keyboard navigation without obvious render failures. +- Webex remote objects may arrive encrypted or incrementally; do not assume list/detail/avatar data is synchronously complete. + +## Module Do's / Don'ts + +- DO normalize destinations through the existing constants/setup path and compose child widgets through package entrypoints. +- DO keep event strings/payload construction centralized in current event/helper files. +- DON'T duplicate SDK fetch/listener logic in presentational components or treat protected examples as newer than code. + +## Export Stability + +Public default/named exports, props, destination strings, browser/data API names, and events are semver-sensitive. Legacy namespace mapping remains: + +| Legacy package | Current package | +|---|---| +| `@ciscospark/widget-space` | `@webex/widget-space` | +| `@ciscospark/widget-message` | `@webex/widget-message` | +| `@ciscospark/widget-files` | `@webex/widget-files` | +| `@ciscospark/widget-roster` | `@webex/widget-roster` | + +## Host Integration & Theming + +Hosts may import React packages or mount the full widget through `window.webex.widget(element).spaceWidget(options)` / `data-toggle="webex-space"`. They provide auth/SDK and destination options, include the package Sass/CSS or CDN stylesheet, and consume callbacks/DOM events. Do not assume host React/theme versions beyond declared root/package dependencies. + +## Key Design Trade-off + +- The full widget favors reusable activity packages and centralized Redux/SDK orchestration over a single monolithic component. This preserves composability and host options but creates cross-package coordination and a larger compatibility surface. + +## Test-Case Strategy (module) + +Jest covers Space actions, reducer, selector, and setup; child/Redux/component tests cover focused behaviors. WebdriverIO supplies the end-to-end contract: activity navigation/roster, message send/receive/events, flags/deletion, files/markdown, meeting lifecycle, guest auth, startup options, data API, multiple widgets, and axe accessibility. Every change should add a positive assertion and a negative/recovery assertion at the lowest reliable tier. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| `SPACE-R-001` destination/setup | `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js`, `test/journeys/specs/space/startup-settings.js` | broader invalid SIP/PSTN cases | +| `SPACE-R-002` activity configuration | `test/journeys/specs/space/startup-settings.js` | focused Jest coverage for every option combination | +| `SPACE-R-003` messaging/files/roster | `test/journeys/specs/space/index.js`, `test/journeys/lib/test-helpers/space-widget/messaging.js`, `test/journeys/lib/test-helpers/space-widget/roster.js` | remote-service dependent | +| `SPACE-R-004` events | `test/journeys/lib/events.js`, `test/journeys/specs/space/index.js` | full payload compatibility snapshots | +| `SPACE-R-005` accessibility | `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/lib/axe.js` | unit keyboard cases for all focus branches | +| `SPACE-R-006` teardown | `packages/node_modules/@webex/widget-space/src/reducer.test.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js` | repeated browser mount/remove leak test | + +## Traceability + +- Repo architecture: `../ARCHITECTURE.md`; registry: `../SPEC_INDEX.md`; contracts: `../CONTRACTS.md`. +- Coverage state, source routing, profiles, and contract baseline: `.sdd/manifest.json`. diff --git a/ai-docs/modules/state-management-spec.md b/ai-docs/modules/state-management-spec.md new file mode 100644 index 000000000..8c93a9888 --- /dev/null +++ b/ai-docs/modules/state-management-spec.md @@ -0,0 +1,238 @@ +# Redux and State Management — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and [`SERVICE_STATE.md`](../SERVICE_STATE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `state-management` | +| Source path(s) | `packages/node_modules/@webex/redux-module-*/`, `react-redux-spark*/` | +| Doc kind | Module spec | +| Coverage score | 94% assessed 2026-07-22; all state packages, public barrels, async conventions, and major invariants covered | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Reducer initial state, action constants/creators, selectors, thunks, tests, and widget composition define this module. Redux state is client-side representation, never evidence that the repository owns durable Webex data. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Legacy `@ciscospark/redux-module-*` READMEs | namespace migration | verified | Export Stability records the suffix-preserving move to `@webex`. | +| Source barrels/actions/reducers/selectors | state contract | authoritative | Public Surface through State Model. | +| Adjacent reducer/action/selector tests | transitions | verified | Test-Case Strategy. | + +## Overview + +Twenty capability packages normalize Webex resources and async operations into Redux/Immutable.js state. `react-redux-spark` owns the SDK instance/authentication slice; feature modules own activities, conversation, spaces, users, media, meetings, presence, Mercury, flags, errors, and related projections. + +## Purpose / Responsibility + +Give widgets deterministic local state transitions around SDK calls and realtime events. This layer does not persist authoritative business data and must not invent server state. + +## Stack + +Redux 3, react-redux 5, redux-thunk, Immutable.js, reselect, recompose/React integration, Webex JS SDK, Jest, and redux-mock-store. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── redux-module-*/src/ +│ ├── actions.js +│ ├── reducer.js +│ ├── selectors.js +│ └── index.js +├── react-redux-spark/src/ # SDK/auth state and enhancer +├── react-redux-spark-metrics/src/ # metrics state/actions +└── react-redux-spark-fixtures/src/ # test-only state fixtures +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/redux-module-activity/src/index.js`, `packages/node_modules/@webex/redux-module-spaces/src/index.js`, `packages/node_modules/@webex/redux-module-users/src/index.js` | representative public action/reducer/selector barrels; every exact package path is indexed in `../CONTRACTS.md` | +| `packages/node_modules/@webex/redux-module-spaces/src/actions.js` | representative action types, creators, and async SDK work | +| `packages/node_modules/@webex/redux-module-spaces/src/reducer.js` | representative initial state and transitions | +| `packages/node_modules/@webex/react-redux-spark/src/index.js` | SDK Redux integration surface | +| `packages/node_modules/@webex/redux-module-meetings/src/actions.js` | meeting lifecycle bridge | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.state.modules` | SDK | `@webex/redux-module-*` barrels | capability reducers/actions/thunks/selectors | public semver; action/export changes require compatibility review | exact `rw.state.*` catalog paths; representative: `packages/node_modules/@webex/redux-module-activity/src/index.js`, `packages/node_modules/@webex/redux-module-spaces/src/index.js` | `../CONTRACTS.md` | +| `rw.state.sdk` | SDK | `@webex/react-redux-spark*` barrels | SDK/auth, metrics, and fixture integration | public semver; fixtures remain test-oriented | `packages/node_modules/@webex/react-redux-spark/src/index.js`, `packages/node_modules/@webex/react-redux-spark-metrics/src/index.js`, `packages/node_modules/@webex/react-redux-spark-fixtures/src/index.js` | `../CONTRACTS.md` | + +Compatibility notes: + +The public state packages include activities, activity, avatar, conversation, errors, features, flags, indicators, media, meetings, Mercury, presence, search, share, spaces, teams, and users. Reducer keys are selected when widget entrypoints compose a store, rather than through one global root reducer. + +## Requires (dependencies) + +- Redux store/provider and thunk middleware assembled by the widget runtime. +- Webex SDK plugins for remote operations and Mercury events. +- Immutable.js-compatible state supplied to selectors/reducers. +- Consumer widgets/containers to select and dispatch module behavior. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `STATE-R-001` | Each module exports a stable reducer/actions/selectors surface through its package barrel. | Widgets compose capability-specific stores and external packages import named operations. | `packages/node_modules/@webex/redux-module-activity/src/index.js`, `packages/node_modules/@webex/redux-module-spaces/src/index.js`, `packages/node_modules/@webex/redux-module-users/src/index.js` | `packages/node_modules/@webex/redux-module-activity/src/actions.test.js`, `packages/node_modules/@webex/redux-module-spaces/src/reducer.test.js` | Some modules intentionally export only selectors/constants. | PRESENT | +| `STATE-R-002` | Async operations dispatch observable request/success/failure transitions around SDK promises. | UI needs deterministic loading and error state. | `packages/node_modules/@webex/redux-module-spaces/src/actions.js`, `packages/node_modules/@webex/redux-module-spaces/src/reducer.js` | `packages/node_modules/@webex/redux-module-spaces/src/actions.test.js`, `packages/node_modules/@webex/redux-module-spaces/src/reducer.test.js` | Exact status vocabulary varies by older module. | PRESENT | +| `STATE-R-003` | Remote resources are keyed/normalized and merged without replacing unrelated entities. | Realtime and request responses arrive incrementally. | `packages/node_modules/@webex/redux-module-activities/src/reducer.js`, `packages/node_modules/@webex/redux-module-spaces/src/reducer.js`, `packages/node_modules/@webex/redux-module-users/src/reducer.js` | `packages/node_modules/@webex/redux-module-spaces/src/reducer.test.js`, `packages/node_modules/@webex/redux-module-users/src/reducer.test.js` | Server conflict resolution remains SDK-owned. | PRESENT | +| `STATE-R-004` | Meetings store identifiers and readiness projections while the SDK meeting collection remains the live object authority. | SDK meeting/media instances are mutable event emitters unsuitable as canonical Redux data. | `packages/node_modules/@webex/redux-module-meetings/src/actions.js`, `packages/node_modules/@webex/redux-module-meetings/src/reducer.js` | `packages/node_modules/@webex/widget-meetings/src/components/MeetingsWidget.test.js` | Some lifecycle branches remain unimplemented. | PRESENT | +| `STATE-R-005` | Errors are exposed to views and reset when a new destination/operation begins where owning logic requests it. | Stale failures must not contaminate a new widget context. | `packages/node_modules/@webex/redux-module-errors/src/reducer.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.js` | `packages/node_modules/@webex/redux-module-errors/src/reducer.test.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js` | Reset ownership is distributed. | PRESENT | +| `STATE-R-006` | SDK auth and instance state are isolated in `react-redux-spark` and shared through enhancers/selectors. | Avoid parallel SDK instances and inconsistent authentication state inside one widget. | `packages/node_modules/@webex/react-redux-spark/src/index.js`, `packages/node_modules/@webex/react-redux-spark/src/reducer.js` | `packages/node_modules/@webex/react-redux-spark/src/reducer.test.js`, `packages/node_modules/@webex/react-redux-spark/src/sdk.test.js` | Separate widgets may intentionally own separate stores. | PRESENT | + +## Design Overview + +Packages follow a small Redux module convention: constants/action creators and thunks produce actions; an Immutable reducer owns a slice; selectors project UI-ready data; `index.js` exposes the supported boundary. Widgets explicitly merge only required reducers, which keeps independently published packages composable. + +## Data Flow + +```mermaid +flowchart LR + UI[Widget/container] -->|dispatch| Thunk[Action/thunk] + Thunk -->|request| SDK[Webex SDK] + Thunk -->|request/success/failure| Reducer[Immutable reducer] + SDK -->|promise/event data| Thunk + Mercury[Mercury event] --> Reducer + Reducer --> State[Widget store] + State --> Selector[Selector] + Selector --> UI +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| dispatch an SDK-backed state operation | Async Redux transition | fulfilled and rejected branches | + +```mermaid +sequenceDiagram + participant V as View + participant A as Thunk + participant S as SDK + participant R as Reducer + V->>A: dispatch operation + A->>R: REQUEST + A->>S: invoke SDK + alt fulfilled + S-->>A: result + A->>R: SUCCESS + normalized payload + else rejected + S-->>A: error + A->>R: FAILURE + end + R-->>V: selected state +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class WidgetStore + class ThunkActions + class ImmutableReducer + class Selectors + class WebexSDK + WidgetStore --> ThunkActions + ThunkActions --> WebexSDK + ThunkActions --> ImmutableReducer + ImmutableReducer --> WidgetStore + Selectors --> WidgetStore +``` + +## Use Cases + +- Load and incrementally update spaces, users, activities, teams, presence, flags, and features. +- Send conversation operations while reflecting request/error state. +- Project SDK authentication/current-instance state into widget setup. +- Track meeting IDs/media readiness and retrieve live meetings from the SDK collection. +- Supply repeatable fixtures and metrics actions for tests/instrumentation. + +## State Model + +- Each reducer owns an Immutable map/list with explicit initial state. +- Common state dimensions are `items/byId`, current entity, operation status, errors, pagination, SDK instance/auth status, and feature/flag values. +- Triggers are widget dispatches, SDK promise outcomes, Mercury events, destination changes, and teardown/reset actions. +- Authoritative remote data remains Webex; Redux is a cache/projection scoped to the widget store. + +## Business Rules & Invariants + +- Reducers are pure and retain unrelated state for unknown actions. +- Entity IDs, not mutable SDK objects, are preferred when referencing live meetings/resources. +- A success/failure action corresponds to its owning request and preserves enough context for the view to decide recovery. +- Selectors do not mutate state and tolerate the initial/unloaded state expected by their consumers. + +## Concurrency & Reactive Flow + +SDK promises and Mercury events can arrive out of order. Reducers merge by resource identity; setup/status guards prevent duplicate subscriptions/fetches. A destination change must reset scoped data or tag operations so late results cannot become the new visible destination. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Idle + Idle --> Requested: request action + Requested --> Ready: success + normalized payload + Requested --> Failed: failure action + Ready --> Requested: refresh/mutation + Failed --> Requested: retry/new context + Ready --> Idle: reset/teardown +``` + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| SDK promise rejects | module failure/error action | view renders error; owner retries or starts a new context | +| SDK is not authenticated/registered | auth/status slice not ready | wait for runtime readiness; do not dispatch dependent work | +| malformed/unrelated realtime payload | ignored or owning error action | retain existing state and inspect SDK/metrics logs | + +## Pitfalls + +- Similar modules use older, non-uniform status shapes; inspect the owning reducer before reusing a selector. +- Redux does not imply persistence or cross-widget sharing. +- Never serialize live SDK meeting/media objects into documentation or new reducer state without an explicit design change. + +## Module Do's / Don'ts + +- Do add action/reducer/selector tests together for a new transition. +- Do compose reducers explicitly at the consuming widget boundary. +- Don't mutate Immutable state or SDK objects in reducers. +- Don't bypass exported barrels from another package without a documented internal reason. + +## Export Stability + +All non-private `@webex/redux-module-*` and `react-redux-spark*` entrypoints are public package contracts. Protected `@ciscospark` rename notices record the same-suffix move to `@webex`; maintain named action/reducer/selector compatibility under semver. + +## Key Design Trade-off + +Capability-local stores prevent a mandatory application-wide schema and support standalone widgets, at the cost of repeated reducer composition and potential duplicated SDK-derived cache state across widget instances. + +## Test-Case Strategy (module) + +| Requirement | Current evidence | Focused gap | +|---|---|---| +| `STATE-R-001` exports | `packages/node_modules/@webex/redux-module-activity/src/index.js`, `packages/node_modules/@webex/redux-module-activity/src/actions.test.js` | automated public-export snapshots | +| `STATE-R-002` async lifecycle | `packages/node_modules/@webex/redux-module-spaces/src/actions.test.js`, `packages/node_modules/@webex/redux-module-spaces/src/reducer.test.js` | cancellation/late-result tests | +| `STATE-R-003` normalized merge | `packages/node_modules/@webex/redux-module-users/src/reducer.test.js` | adversarial ordering | +| `STATE-R-004` meeting references | `packages/node_modules/@webex/redux-module-meetings/src/reducer.js` | destroyed/stopped event coverage | +| `STATE-R-005` error reset | `packages/node_modules/@webex/redux-module-errors/src/reducer.test.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js` | cross-destination late failure | +| `STATE-R-006` SDK auth | `packages/node_modules/@webex/react-redux-spark/src/sdk.test.js`, `packages/node_modules/@webex/react-redux-spark/src/reducer.test.js` | multi-widget isolation | + +## Traceability + +- System state boundaries: `../SERVICE_STATE.md`; contracts: `../CONTRACTS.md`. +- Redux conventions: `../patterns/redux-module-barrel.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/test-automation-spec.md b/ai-docs/modules/test-automation-spec.md new file mode 100644 index 000000000..92ed78686 --- /dev/null +++ b/ai-docs/modules/test-automation-spec.md @@ -0,0 +1,262 @@ +# Test Automation — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and repository [`REVIEW_CHECKLIST.md`](../REVIEW_CHECKLIST.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `test-automation` | +| Source path(s) | adjacent `*.test.js`, `test/journeys/`, `jest.config.json`, `wdio.conf.js`, `scripts/tests/` | +| Doc kind | Module spec | +| Coverage score | 96% assessed 2026-07-22; unit/journey topology, protected plan intent, suites, environment, fixtures, and CI use covered | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Executable Jest/WDIO configuration and test code determine current coverage. The protected journey test plan preserves intended scenarios; where prose and executable suites diverge, this spec records the gap instead of claiming a pass. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Protected journey plan | journey intent | verified/reconciled | Suite inventory and scenario expectations are preserved below and mapped to executable specs. | +| Repository usage-guide test commands | developer workflow | verified | Public Surface and Use Cases. | +| Jest/WDIO/CircleCI configuration | execution | authoritative | Requirements, Design Overview, Error Handling. | +| adjacent tests and journey specs/helpers | behavior | authoritative | Test-Case Strategy. | + +## Overview + +The repository uses adjacent Jest suites for package-level units and WebdriverIO journeys for real browser/widget integration. Journeys cover smoke, Space, Recents, production TAP, data API, browser-global, multiple-widget, guest, startup-setting, event, media, and accessibility behavior. CircleCI runs lint/Jest and configured Chrome/Firefox integration jobs. + +## Purpose / Responsibility + +Detect regressions in independently published packages and embedded widget behavior, while keeping external-service requirements and evidence limits explicit. + +## Stack + +Jest 24, Babel setup, identity/file/sound mocks, react-test-renderer, redux-mock-store, WebdriverIO 7/Mocha/Chai, Sauce Labs or local Selenium, static server service, Webex test-user helpers, Axe, CircleCI, Chrome, and Firefox. + +## Folder / Package Structure + +```text +jest.config.json +packages/node_modules/@webex/**/src/*.test.js +scripts/tests/ # setup, async/openh264, suite preparation +test/journeys/ +├── specs/ +│ ├── smoke/ +│ ├── space/ +│ ├── recents/ +│ └── tap/ +├── lib/ # browser, users, events, helpers, waiters, axe +├── server/ # local/global/data-api pages +└── testplan.md # protected intent source +wdio.conf.js +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `jest.config.json` | unit roots, transforms, aliases, mocks, setup | +| `scripts/tests/jest-setup.js` | global test environment | +| `wdio.conf.js` | suites, browsers, Sauce/local services, timeouts, reporters | +| `test/journeys/specs/**/*.js` | executable integration assertions | +| `test/journeys/lib/test-helpers/` | widget actions/assertion helpers | +| `test/journeys/testplan.md` | protected human scenario inventory | +| `.circleci/config.yml` | CI test invocation and artifacts | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.cmd.static-analysis` | CLI | `npm run static-analysis`, `npm run eslint` | repository lint gate | CI/developer contract | `package.json` | `../CONTRACTS.md` | +| `rw.cmd.jest` | CLI | `npm run jest`, `npm test` | package units; combined lint/unit gate | CI/developer contract | `package.json`, `jest.config.json` | `../CONTRACTS.md` | +| `rw.cmd.journeys` | CLI | `npm run test:automation[:smoke|:space|:recents]` | local/remote browser suites | suite names stable for CI | `package.json`, `wdio.conf.js` | `../CONTRACTS.md` | +| `rw.cmd.tap` | CLI | `npm run test:tap`, `npm run test:integration` | production TAP or smoke integration | explicit environment/target required | `package.json`, `wdio.conf.js` | `../CONTRACTS.md` | + +Compatibility notes: + +WDIO accepts `BROWSER`, `VERSION`, `PLATFORM`, `SAUCE`, `TAP`, `INTEGRATION`, `JOURNEY_TEST_BASE_URL`, `STATIC_SERVER_PATH`, `BUILD_NUMBER`, test-user/service variables, and Sauce credentials. + +## Requires (dependencies) + +Installed npm dependencies and built widget distributions; Chrome/Firefox plus local Selenium or Sauce credentials; Webex test users and integration endpoints for remote journeys; fake media device settings; writable report/artifact locations. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `TEST-R-001` | Jest discovers adjacent `*.test.js` only under tracked package source and maps `@webex`/legacy aliases to source. | Unit tests must exercise repository code, not published builds. | `jest.config.json` | `packages/node_modules/@webex/widget-space/src/reducer.test.js`, `packages/node_modules/@webex/widget-recents/src/enhancers/setup.test.js` | TypeScript-named tests outside regex need explicit support. | PRESENT | +| `TEST-R-002` | Smoke verifies Space, Recents, multiple widgets, demo auth modes, core messaging/calling, events, and accessibility. | Every PR needs a bounded integration signal across primary embeddings. | `wdio.conf.js`, `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/specs/smoke/widget-recents/index.js`, `test/journeys/specs/smoke/multiple/index.js`, `test/journeys/specs/smoke/demo.js` | same executable smoke specs | Calling breadth is limited to composed Space behavior. | PRESENT | +| `TEST-R-003` | Space journeys cover global/data API, messaging/actions/files/markdown, roster, call lifecycle, guest access, startup settings, events, and accessibility. | Space is a primary embeddable product surface. | `test/journeys/specs/space/index.js`, `test/journeys/specs/space/guest.js`, `test/journeys/specs/space/startup-settings.js`, `test/journeys/specs/space/data-api.js` | same executable Space specs | External services/test users can cause non-product failures. | PRESENT | +| `TEST-R-004` | Recents journeys cover global/data API, group/one-to-one updates, unread/read/select/member events, filters, startup settings, incoming-call indicators, and accessibility. | Recents must react correctly to SDK-driven changes in both host APIs. | `test/journeys/specs/recents/dataApi/basic.js`, `test/journeys/specs/recents/global/basic.js`, `test/journeys/specs/recents/dataApi/space-list-filter.js`, `test/journeys/specs/recents/global/startup-settings.js` | same executable Recents specs | Some plan wording predates current filters/settings specs. | PRESENT | +| `TEST-R-005` | Browser runs use fake media/notification settings, bounded waits, prepared test users, and stored diagnostics/reports. | Realtime/media UI requires reproducible automation and debuggable failures. | `wdio.conf.js`, `scripts/tests/beforeSuite.js`, `test/journeys/lib/wait-for-mercury-event.js`, `test/journeys/lib/axe.js` | `.circleci/config.yml` | Network timing remains nondeterministic. | PRESENT | +| `TEST-R-006` | Contributors run focused full widget journeys before PR; CI runs configured smoke/integration plus lint/Jest. | Local ownership and CI gates divide expensive coverage responsibly. | `test/journeys/testplan.md`, `.circleci/config.yml` | `package.json` | CI workflow conditions require independent check. | PRESENT | + +## Design Overview + +Unit suites stay beside implementation and mock styles/assets/dependencies through Jest. Browser journeys drive static test pages and Webex APIs using reusable helpers, waiting for SDK/Mercury outcomes rather than only DOM timing. WDIO selects suite/environment and records JUnit/browser artifacts; CI builds distributions before journeys. + +## Data Flow + +```mermaid +flowchart LR + Source[Package source] --> Jest[Jest + mocks] + Source --> Build[Journey bundles] + Build --> Static[Static test server] + Static --> WDIO[WDIO browser] + Helpers[Test users/SDK helpers] --> WDIO + Webex[Integration services] --> WDIO + Jest --> Reports[Results] + WDIO --> Reports + Reports --> CI[CI gate/artifacts] +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| deterministic package units | Jest source suite | transform/setup/assertion failure blocks the command | +| built browser journeys | WDIO integration suite | build/auth/setup/assertion failures retain diagnostics and block CI | + +```mermaid +sequenceDiagram + participant D as Developer/CI + participant J as Jest + participant C as Jest config/setup + participant S as Package source/tests + D->>J: npm run jest + J->>C: load roots, transforms, aliases, mocks + C->>S: discover and execute .test.js suites + alt all assertions pass + S-->>D: success + else setup/transform/assertion failure + S-->>D: failed suite and diagnostics + end +``` + +```mermaid +sequenceDiagram + participant C as CI/developer + participant B as Build/static server + participant W as WDIO browser + participant X as Webex test services + C->>B: build selected widget distributions + C->>W: start suite with environment + W->>B: load global/data/demo test page + W->>X: prepare users and trigger SDK behavior + X-->>W: API/Mercury/call result + W-->>C: assertion, JUnit, screenshots/logs + alt assertion/setup failure + C-->>C: retain diagnostics and block gate + end +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class JestConfig + class AdjacentUnitTests + class WdioConfig + class JourneySpecs + class JourneyHelpers + class StaticPages + class CircleCI + JestConfig --> AdjacentUnitTests + WdioConfig --> JourneySpecs + JourneySpecs --> JourneyHelpers + JourneySpecs --> StaticPages + CircleCI --> JestConfig + CircleCI --> WdioConfig +``` + +## Use Cases + +- Run all deterministic package suites with UTC timezone and repository source aliases. +- Run smoke locally against built distributions before opening a PR. +- Run the full Space or Recents suite for a changed primary widget. +- Exercise production assets through TAP without starting the local static server. +- Diagnose a failed remote browser run from JUnit, browser artifacts, build identity, and Sauce session. + +Protected-plan scenario inventory: + +- **Recents smoke:** group and one-to-one incoming messages, read/unread behavior, new one-to-one, call indicators, created/read/unread/selected/member events, and Axe. +- **Space smoke:** activity-menu open/close and Message/Meet/Files/Roster switches, roster count/list/close, send/receive, call/hangup, and Axe. +- **Multiple/demo smoke:** Space and Recents on one page; demo access-token and SDK-instance authentication; external activity control. +- **Space full:** header/menu/roster/search/add; send/receive/events; flag/unflag; self-delete/no other-delete; PNG/file tab; bold, italic, quote, lists, H1-H3, horizontal rule, link, inline code, and code block. +- **Space call/guest/settings/data API:** precall, hangup before answer, decline, hangup, call payload; guest messaging/calls; `userId`, disabled activities/error, initial Meet/Message, start-call; both data and global instantiation. +- **Recents full:** data/global group and one-to-one update/read/call-hover; new one-to-one; incoming-call progress; current events; filters/startup settings; Axe. + +## Business Rules & Invariants + +- A test claiming a current event uses a constant/runtime event still present in source. +- Integration tests prepare/clean isolated users/spaces and wait for observable Webex outcomes. +- Tests never commit credentials, access tokens, or Sauce secrets. +- Accessibility assertions are required in the existing smoke/full surfaces and are not replaced by snapshot tests. + +## Concurrency & Reactive Flow + +Mercury, SDK promises, call/media setup, browsers, and remote test-user services are asynchronous. Helpers use bounded waits and event synchronization; arbitrary sleeps are a last resort. Parallel sessions require unique build/tunnel/user context and must not share mutable conversations unintentionally. + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| setup/build/auth failure | before-suite or WDIO setup failure | fix environment/asset/user preparation before interpreting product assertions | +| assertion/timeout | failed Jest/WDIO result with reports/artifacts | inspect event timing, browser artifacts, and build identity | +| local static assets missing | page/load failure | build journey/widget assets or configure a remote base URL | +| repeated environmental flake | reproducible intermittent failure | assign owner/expiry and fix synchronization; do not hide with retries | + +## Pitfalls + +- The protected plan describes intent, not proof that every scenario still executes. +- Jest `testRegex` is `.test.js$`; TypeScript test naming requires validation. +- Browser journeys depend on mutable external services and credentials. +- Smoke is not a substitute for the changed widget's full suite. + +## Module Do's / Don'ts + +- Do add the narrowest deterministic unit test and the relevant journey for public behavior. +- Do use event/wait helpers and preserve diagnostics. +- Don't weaken assertions to accommodate timing without proving the race. +- Don't run production TAP against unapproved targets or expose credentials in logs. + +## Key Design Trade-off + +Real Webex/browser journeys give high-confidence integration evidence but are expensive and environment-sensitive, so adjacent unit tests provide fast breadth while focused full suites and CI smoke provide layered assurance. + +## Test-Case Strategy (module) + +| Layer | Scope | Expected gate | +|---|---|---| +| Static analysis | all tracked JS/TS/config/docs according to ESLint ignores | `npm run static-analysis` passes | +| Jest | 107 adjacent `*.test.js` suites discovered under `packages/node_modules/@webex/` | `npm run jest` passes | +| Smoke integration | Space, Recents, multiple, demo | required CI integration signal | +| Focused Space | messaging/files/roster/calls/guest/settings/data API | run for Space-related changes | +| Focused Recents | global/data API/filter/settings/events | run for Recents-related changes | +| TAP | production-hosted selected widget flows | explicit production validation | +| Accessibility | Axe in protected smoke/full surfaces | zero blocking violations | + +| Requirement | Existing test/config evidence | Focused gap | +|---|---|---| +| `TEST-R-001` Jest discovery | `jest.config.json`, `packages/node_modules/@webex/widget-space/src/reducer.test.js` | TypeScript test discovery | +| `TEST-R-002` smoke | `wdio.conf.js`, `test/journeys/specs/smoke/widget-space/index.js`, `test/journeys/specs/smoke/widget-recents/index.js` | dedicated calling widgets | +| `TEST-R-003` Space | `test/journeys/specs/space/index.js`, `test/journeys/specs/space/guest.js`, `test/journeys/specs/space/data-api.js` | remote-service failure isolation | +| `TEST-R-004` Recents | `test/journeys/specs/recents/global/basic.js`, `test/journeys/specs/recents/dataApi/basic.js` | stale-event negative assertions | +| `TEST-R-005` browser setup | `scripts/tests/beforeSuite.js`, `test/journeys/lib/wait-for-mercury-event.js` | bounded wait consistency | +| `TEST-R-006` contributor/CI gates | `test/journeys/testplan.md`, `.circleci/config.yml` | automated plan-to-suite drift check | + +Coverage gaps to retain: dedicated calling-widget journeys are sparse; several TypeScript calling components have stories rather than unit suites; teardown/race/error paths need focused characterization. + +## Traceability + +- Quality gate commands: `../GETTING_STARTED.md`, `../REVIEW_CHECKLIST.md`. +- Capability requirement mappings: every file in `ai-docs/modules/`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/modules/widget-runtime-auth-spec.md b/ai-docs/modules/widget-runtime-auth-spec.md new file mode 100644 index 000000000..89593ad4c --- /dev/null +++ b/ai-docs/modules/widget-runtime-auth-spec.md @@ -0,0 +1,251 @@ +# Widget Runtime, Authentication, and Demos — SPEC + +> Start with root [`AGENTS.md`](../../AGENTS.md), router [`SPEC_INDEX.md`](../SPEC_INDEX.md), and system [`ARCHITECTURE.md`](../ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | `widget-runtime-auth` | +| Source path(s) | `packages/node_modules/@webex/webex-widget-base/`, `webex-sign-in-page/`, `widget-*-demo/`, `samples/` | +| Doc kind | Module spec | +| Coverage score | 93% assessed 2026-07-22; enhancer order, host APIs, auth, teardown, and demo boundaries covered | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | `codex-desktop` / repository owner / 2026-07-23 | +| Validation status | independent Cursor validation passed on 2026-07-23 with zero Blocking findings | + +## Evidence Rules + +Current enhancer signatures/order, package exports, host API implementation, tests, and demo wiring are authoritative. The protected widget-base README is reconciled: its two-argument default quick start conflicts with current implementation and is not promoted. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Protected base usage guidance | enhancer and host usage | corrected | Object-shaped `constructWebexEnhancer` is the supported source-backed construction path; conflict is in Pitfalls. | +| Protected demo guidance | demo startup | verified | Use Cases and Host Integration. | +| Repository usage guidance | browser/data API usage | verified/expanded | Current runtime surfaces are in Public Surface; its limited widget list remains unchanged. | +| legacy rename READMEs | namespace migration | verified | Export Stability. | + +## Overview + +`webex-widget-base` turns a React component into an embeddable widget by composing data API registration, browser globals, a Redux store, removal, SDK authentication, current-user loading, display name, and version metadata. The sign-in and demo/sample packages provide development/host entrypoints around this runtime. + +## Purpose / Responsibility + +Own widget instantiation, per-widget store/SDK/auth wiring, browser/data-attribute host APIs, localization hooks, removal, and developer demo shells. Capability widgets own domain behavior. + +## Stack + +React/ReactDOM, recompose, Redux/react-redux, Webex JS SDK, react-intl, ampersand-events, CustomEvent, Babel/Webpack development runtime, TypeScript for sign-in UI, and Jest/WebdriverIO. + +## Folder / Package Structure + +```text +packages/node_modules/@webex/ +├── webex-widget-base/src/ +│ └── enhancers/ # data API, browser globals, store, removal, intl, current user +├── webex-sign-in-page/src/ +├── widget-demo/src/ # executable shared demo shell +├── widget-space-demo/src/ # index.html only; package src entry is absent +└── widget-recents-demo/src/ # index.html only; package src entry is absent +samples/ # host examples served by tooling +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `packages/node_modules/@webex/webex-widget-base/src/index.js` | canonical enhancer composition/order and exports | +| `packages/node_modules/@webex/webex-widget-base/src/enhancers/withDataAPI.js` | data-attribute discovery/instantiation | +| `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | global registry, lookup, host events | +| `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js` | per-widget Redux store | +| `packages/node_modules/@webex/webex-widget-base/src/enhancers/withRemoveWidget.js` | injected `REMOVE_WIDGET` action; browser `remove()` does not dispatch it | +| `packages/node_modules/@webex/webex-widget-base/src/enhancers/withCurrentUser.js` | authenticated user loading | +| `packages/node_modules/@webex/webex-sign-in-page/src/index.ts` | sign-in page public barrel | +| `packages/node_modules/@webex/widget-demo/src/index.js` | development demo mount | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| `rw.runtime.base` | SDK/React | `constructWebexEnhancer({name, reducers, enhancers})`, default `WebexWidgetBase(name, BaseComponent)`, `withIntl`, `withInitialState`, `withBrowserGlobals` | common runtime composition | public semver; `enhancers` is accepted/documented by the entrypoint but ignored by `withInitialState`; the default helper has the signature mismatch below | `packages/node_modules/@webex/webex-widget-base/src/index.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js` | `../CONTRACTS.md` | +| `rw.host.widget.select` | browser API | `window.webex.widget(element)` | select/create a browser widget | stable global/alias and lookup behavior | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | `../CONTRACTS.md` | +| `rw.host.widget.mount` | browser API | `widget.{name}Widget(options)` | mount a registered capability | names/options are public | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | `../CONTRACTS.md` | +| `rw.host.widget.remove` | browser API | `widget.remove(callback?) -> Promise` | unmount React and delete the UUID registry entry | callback/promise and registry semantics are public; no Redux reset is dispatched | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js` | `../CONTRACTS.md` | +| `rw.host.data-api` | data API | `[data-toggle^="webex-{name}"]` + `data-*` | discover and mount widgets | names and kebab-to-camel mapping are public | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withDataAPI.js` | `../CONTRACTS.md` | +| `rw.runtime.sign-in` | SDK/React | `webex-sign-in-page` typed barrel | reusable sign-in UI | public semver | `packages/node_modules/@webex/webex-sign-in-page/src/index.ts` | `../CONTRACTS.md` | + +Compatibility notes: + +Data API hosts instantiate supported widget elements/attributes after runtime discovery. + +The default `WebexWidgetBase(name, BaseComponent)` export currently passes `name` to an object-destructuring function; consumers must not treat the protected README example as validated until that implementation conflict is resolved. + +## Requires (dependencies) + +React DOM, Redux reducer maps supplied by each widget, capability HOCs composed outside the base constructor, Webex SDK credentials or instance, browser globals/CustomEvent support for embedded APIs, locale/intl data, and bundler-injected `REACT_WEBEX_VERSION`. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| `RUNTIME-R-001` | Enhancers execute in the declared compose order: data API, browser globals, initial state, removal, SDK, current user, display name, version. | Outer/inner ordering controls host discovery, store availability, auth, and teardown. | `packages/node_modules/@webex/webex-widget-base/src/index.js` | `test/journeys/specs/smoke/multiple/index.js` | No direct base composition test; reordering requires characterization. | PRESENT | +| `RUNTIME-R-002` | Every instantiated widget receives its own configured Redux store and SDK/auth context; the constructor's `enhancers` field has no effect because `withInitialState` ignores it. | Multiple widgets need separate state, and consumers must not rely on a documented-but-unused option. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js`, `packages/node_modules/@webex/webex-widget-base/src/index.js` | None found for store isolation. | The multiple-widget journey proves coexistence, not store-object isolation. | PRESENT | +| `RUNTIME-R-003` | Browser/data APIs discover, register, look up, emit from, and remove widgets using stable names/UUIDs. | Non-React hosts depend on embedding contracts. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withDataAPI.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withRemoveWidget.js` | `test/journeys/specs/space/data-api.js`, `test/journeys/specs/smoke/multiple/index.js` | Exact DOM payloads require independent validation. | PRESENT | +| `RUNTIME-R-004` | SDK-dependent child setup waits for valid auth/registration/current-user state. | Prevent unauthenticated remote calls and incomplete identity UI. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withCurrentUser.js`, `packages/node_modules/@webex/react-redux-spark/src/index.js` | `packages/node_modules/@webex/react-redux-spark/src/sdk.test.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js` | Credentials are environment-owned. | PRESENT | +| `RUNTIME-R-005` | Browser `remove()` unmounts React, deletes the UUID registry entry, and returns through the established promise/callback contract; it does not dispatch `REMOVE_WIDGET` or explicitly reset Redux. | Embedded hosts need the exact implemented teardown boundary and must not assume a state-reset side effect. | `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withRemoveWidget.js`, `packages/node_modules/@webex/webex-widget-base/src/enhancers/withInitialState.js` | None found for browser removal semantics. | Child unmount cleanup is distributed; repeated-remove and store-retention behavior need characterization. | PRESENT | +| `RUNTIME-R-006` | Demo/sample packages remain development shells and are not published as production capability contracts when marked private. | Avoid treating example configuration/token handling as supported API. | `packages/node_modules/@webex/widget-demo/package.json`, `packages/node_modules/@webex/widget-demo/src/index.js`, `scripts/start/commands/demo.js` | `test/journeys/specs/smoke/demo.js` | Some public sign-in UI is shared with demos. | PRESENT | + +## Design Overview + +The runtime is an enhancer pipeline around a capability component. Store and SDK concerns are injected below host-facing wrappers, allowing browser and data APIs to instantiate the same React export. Widgets provide reducer maps; capability-specific HOCs are composed around the base enhancer in their own entrypoints. Although the base constructor accepts an `enhancers` field, the current store initializer ignores it. Demos mount packaged widgets in development-only shells. + +## Data Flow + +```mermaid +flowchart LR + Host[React/browser/data host] --> Registry[Data API + browser globals] + Registry --> Store[Per-widget Redux store] + Store --> Auth[Webex SDK/auth] + Auth --> User[Current-user load] + User --> Widget[Capability component] + Widget --> Events[Callbacks/CustomEvents] + Events --> Host + Host --> Remove[remove contract] + Remove --> Registry +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| discover, register, authenticate, render, emit, remove | Widget instance lifecycle | auth/lookup/removal failures and the setup/removal race are specified in Error Handling | + +```mermaid +sequenceDiagram + participant H as Host + participant B as Base runtime + participant R as Registry/store + participant S as Webex SDK + participant W as Widget + H->>B: instantiate name + options/element + B->>R: allocate UUID and Redux store + B->>S: create/use SDK and authenticate + S-->>B: authenticated/current user + B->>W: render injected props + W-->>H: callback/DOM event + H->>B: remove + B->>R: unmount and clear registry entry (no Redux reset dispatch) +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class HostAPI + class ConstructWebexEnhancer + class ReduxStore + class WebexSDK + class CapabilityWidget + class DemoShell + HostAPI --> ConstructWebexEnhancer + ConstructWebexEnhancer --> ReduxStore + ConstructWebexEnhancer --> WebexSDK + ConstructWebexEnhancer --> CapabilityWidget + DemoShell --> HostAPI +``` + +## Use Cases + +- Embed a packaged widget directly as React. +- Discover and instantiate a widget from supported DOM/data attributes. +- Look up a widget instance, listen for events, and remove it from a non-React host. +- Authenticate/use a Webex SDK instance and load the current user before capability setup. +- Run the local demo or sample server to exercise Space/Recents integrations. + +## State Model + +The runtime tracks a per-widget Redux store, SDK/auth state, current-user fetch state, widget UUID/registry entry, and removal lifecycle. Inputs are host properties/attributes, credentials/SDK instance, reducer maps, locale, and DOM mount points. Capability entrypoints compose additional HOCs outside the base constructor. + +## Business Rules & Invariants + +- `withDataAPI` remains outermost so host discovery wraps the completed widget. +- Store initialization precedes SDK/current-user consumers. +- Widget names and UUID registry keys are stable within an instance lifecycle. +- Removal is idempotent from a host perspective and must not leave a usable stale registry entry. + +## Concurrency & Reactive Flow + +DOM discovery, SDK authentication, current-user fetches, widget events, and removal are asynchronous. A removal racing setup must prevent late results from re-registering or updating an unmounted instance. + +## State Machine + +```mermaid +stateDiagram-v2 + [*] --> Discovered + Discovered --> Registered: allocate registry/store + Registered --> Authenticating + Authenticating --> Ready: SDK + current user ready + Authenticating --> Error: auth/user failure + Ready --> Removing: host remove + Error --> Removing + Removing --> Removed +``` + +## UI Flow + +Hosts see discovery/mount, authentication/loading or sign-in UI, the capability widget, and error/removal states. Demo UI supplies credentials/configuration and must not expose production secrets or redefine widget behavior. + +## Error Handling & Failure Modes + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| authentication/current-user failure | SDK/error state; capability remains unready | correct credentials or retry through owning host | +| invalid host element/name | discovery/lookup does not return a valid instance | correct mount markup/name and instantiate again | +| browser unmount returns `false` | callback receives or Promise resolves `false`; UUID entry is still deleted | inspect the host element and avoid assuming Redux reset occurred | +| remove races setup | late async result after removal | ignore/cancel result; do not re-register instance | + +## Pitfalls + +- Protected base documentation shows `WebexWidgetBase(name, Component)`, but current default implementation conflicts with `constructWebexEnhancer`'s object signature. Prefer the named object-shaped constructor pending a code/API decision. +- Global registries make tests order-sensitive unless reset. +- Demo token handling is development guidance, not a production authentication design. + +## Module Do's / Don'ts + +- Do preserve enhancer order and characterize it before edits. +- Do treat widget removal and listener cleanup as one lifecycle. +- Don't put capability reducers into the base package. +- Don't promote private demo/sample APIs as supported exports. + +## Export Stability + +`@webex/webex-widget-base` and `@webex/webex-sign-in-page` are public. Protected `@ciscospark` notices record namespace migration for legacy runtime packages. Browser/data API names, emitted event translation, and removal behavior are compatibility surfaces as well as JavaScript exports. + +## Host Integration & Theming + +Hosts may use React, browser globals, or data attributes. They provide DOM roots, credentials/SDK/options, locale, and event listeners; the runtime supplies base fonts/styles, Redux/provider context, and version metadata. Multiple widgets must coexist without ID/store collisions. + +## Key Design Trade-off + +One enhancer pipeline makes independently packaged widgets consistently embeddable, but ordering and global-registration behavior create implicit coupling. The canonical object-shaped constructor and lifecycle tests are the guardrails. + +## Test-Case Strategy (module) + +| Requirement | Current evidence | Focused gap | +|---|---|---| +| `RUNTIME-R-001` order | `packages/node_modules/@webex/webex-widget-base/src/index.js`; no direct unit test found | explicit composition-order characterization | +| `RUNTIME-R-002` isolation | None found | concurrent store/SDK identity assertions and unused-`enhancers` characterization | +| `RUNTIME-R-003` host APIs | `test/journeys/specs/space/data-api.js`, `test/journeys/specs/smoke/multiple/index.js` | invalid lookup and repeated remove | +| `RUNTIME-R-004` auth | `packages/node_modules/@webex/react-redux-spark/src/sdk.test.js`, `packages/node_modules/@webex/widget-space/src/enhancers/setup.test.js` | auth rejection/recovery | +| `RUNTIME-R-005` teardown | None found | direct unmount/registry assertions, explicit no-reset characterization, setup/remove race, and listener audit | +| `RUNTIME-R-006` demos | `test/journeys/specs/smoke/demo.js` | production-build exclusion | + +## Traceability + +- Runtime/contract overview: `../ARCHITECTURE.md`, `../CONTRACTS.md`. +- Composition pattern and cleanup rule: `../patterns/widget-enhancer-composition.md`, `../rules/clean-up-runtime-listeners.md`. +- Machine coverage/profile/contracts: `.sdd/manifest.json`. diff --git a/ai-docs/patterns/README.md b/ai-docs/patterns/README.md new file mode 100644 index 000000000..d184e1074 --- /dev/null +++ b/ai-docs/patterns/README.md @@ -0,0 +1,13 @@ +# Repository patterns + +Code-grounded conventions that appear in at least three real files and are not fully explained by ESLint. Use the example files here before adding a component, Redux module, or widget entrypoint. + +## Use Patterns For + +- Choosing the established package-entrypoint, Redux-barrel, and widget-enhancer shapes for new or changed code. +- Comparing a proposed implementation with correct and incorrect repository-grounded examples. +- Finding the concrete source paths from which each convention was extracted. + +- `react-component-entrypoint.md` — PropTypes/defaults/component/default-export shape. +- `redux-module-barrel.md` — actions plus reducer/initial-state package surface. +- `widget-enhancer-composition.md` — widget entrypoints composed through the shared runtime. diff --git a/ai-docs/patterns/react-component-entrypoint.md b/ai-docs/patterns/react-component-entrypoint.md new file mode 100644 index 000000000..ae53785de --- /dev/null +++ b/ai-docs/patterns/react-component-entrypoint.md @@ -0,0 +1,42 @@ +# Pattern: React component entrypoint + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md). + +## When to use + +**When to use:** adding or maintaining a legacy JavaScript `react-component-*` package whose public entrypoint is the component itself. + +## Correct + +```js +// Shape used by packages/node_modules/@webex/react-component-avatar/src/index.js +const propTypes = {/* public props */}; +const defaultProps = {/* optional defaults */}; + +function Component(props) {/* render */} + +Component.propTypes = propTypes; +Component.defaultProps = defaultProps; +export default Component; +``` + +## Incorrect + +```js +// An untyped anonymous default with hidden defaults +export default (props) => renderSomething(props); +``` + +**Why wrong:** consumers/tests lose the explicit prop/default contract and debugging display identity; adjacent packages cannot follow a consistent entrypoint shape. + +## Where it appears + +- `packages/node_modules/@webex/react-component-avatar/src/index.js` +- `packages/node_modules/@webex/react-component-button/src/index.js` +- `packages/node_modules/@webex/react-component-error-display/src/index.js` +- `packages/node_modules/@webex/react-component-textarea/src/index.js` + +## Edge cases / exceptions + +- TypeScript calling components use exported interfaces/types rather than PropTypes. +- Connected components may export a named unconnected implementation and a wrapped default. diff --git a/ai-docs/patterns/redux-module-barrel.md b/ai-docs/patterns/redux-module-barrel.md new file mode 100644 index 000000000..69f6d3e1b --- /dev/null +++ b/ai-docs/patterns/redux-module-barrel.md @@ -0,0 +1,36 @@ +# Pattern: Redux module barrel + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md). + +## When to use + +**When to use:** exposing a legacy Redux capability package to widgets/containers. + +## Correct + +```js +// Shape used by packages/node_modules/@webex/redux-module-spaces/src/index.js +export * from './actions'; +export {default, initialState} from './reducer'; +``` + +## Incorrect + +```js +// Consumers reach into internal files and bypass the package contract +import reducer from '@webex/redux-module-spaces/src/reducer'; +``` + +**Why wrong:** deep imports couple consumers to internal layout and make action/reducer/export changes harder to version safely. + +## Where it appears + +- `packages/node_modules/@webex/redux-module-spaces/src/index.js` +- `packages/node_modules/@webex/redux-module-users/src/index.js` +- `packages/node_modules/@webex/redux-module-teams/src/index.js` +- `packages/node_modules/@webex/redux-module-conversation/src/index.js` + +## Edge cases / exceptions + +- Existing repository-internal deep imports are legacy exceptions; do not create new ones without a boundary rationale. +- Small modules such as features/search implement reducer/actions together in `index.js` but retain the same public concepts. diff --git a/ai-docs/patterns/widget-enhancer-composition.md b/ai-docs/patterns/widget-enhancer-composition.md new file mode 100644 index 000000000..e8a23bb90 --- /dev/null +++ b/ai-docs/patterns/widget-enhancer-composition.md @@ -0,0 +1,38 @@ +# Pattern: Widget enhancer composition + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md). + +## When to use + +**When to use:** creating or maintaining a legacy Webex widget that needs the shared browser/data API, Redux, SDK/auth, current-user, teardown, intl, and version behavior. + +## Correct + +```js +// Shape used by packages/node_modules/@webex/widget-space/src/index.js +export default compose( + constructWebexEnhancer({name: 'space', reducers}), + withIntl({locale: 'en', messages}) +)(ConnectedWidget); +``` + +## Incorrect + +```js +// Directly mount a widget and create an unrelated SDK/store lifecycle +export default ConnectedWidget; +``` + +**Why wrong:** direct export skips host registration, standard teardown, SDK state, version metadata, data attributes, and shared store composition. + +## Where it appears + +- `packages/node_modules/@webex/widget-space/src/index.js` +- `packages/node_modules/@webex/widget-recents/src/index.js` +- `packages/node_modules/@webex/widget-meetings/src/index.js` +- `packages/node_modules/@webex/widget-message/src/index.js` + +## Edge cases / exceptions + +- Newer TypeScript calling widgets expose adapter-context React components and do not use the legacy enhancer stack. +- Private demo entrypoints may mount already-enhanced widgets rather than define a new public widget. diff --git a/ai-docs/rules/README.md b/ai-docs/rules/README.md new file mode 100644 index 000000000..c472d3bbe --- /dev/null +++ b/ai-docs/rules/README.md @@ -0,0 +1,12 @@ +# Detailed repository rules + +Root `AGENTS.md` contains the critical subset and `../RULES.md` contains the catalog. Read these focused rules when changing public packages or asynchronous browser/SDK behavior. + +## Use Rules For + +- Reviewing public-export and runtime-lifecycle changes against explicit must/must-not constraints. +- Locating the tests or CI checks that enforce a rule. +- Keeping detailed rationale out of the compact root rule catalog. + +- `preserve-public-entrypoints.md` +- `clean-up-runtime-listeners.md` diff --git a/ai-docs/rules/clean-up-runtime-listeners.md b/ai-docs/rules/clean-up-runtime-listeners.md new file mode 100644 index 000000000..bc7a1500a --- /dev/null +++ b/ai-docs/rules/clean-up-runtime-listeners.md @@ -0,0 +1,19 @@ +# Rule: Clean up runtime listeners and mounted state + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md). + +## Rule + +Every browser, SDK, media, timer, observer, or realtime listener introduced by a component/HOC must have a matching teardown path. Browser widget removal must unmount React and clear the UUID registry entry; do not assume it dispatches `REMOVE_WIDGET` or explicitly clears Redux state. + +## Why + +Widgets can be mounted and removed repeatedly inside a host page. Leaked listeners duplicate events, retain credentials/resource state, and produce stale UI or media behavior. + +## How to follow + +Follow cleanup examples in `packages/node_modules/@webex/webex-widget-base/src/enhancers/withBrowserGlobals.js`, `packages/node_modules/@webex/widget-voice-mail/src/hooks/useAudio.ts`, and `packages/node_modules/@webex/widget-number-pad/src/SearchContacts/CallSelectPopover.tsx`. Add a negative test proving callbacks stop after unmount/removal. + +## Enforced by + +Jest lifecycle tests and review checks C4/C5/K1; review only for integration leaks not observable in unit tests. diff --git a/ai-docs/rules/preserve-public-entrypoints.md b/ai-docs/rules/preserve-public-entrypoints.md new file mode 100644 index 000000000..4c0cb3d15 --- /dev/null +++ b/ai-docs/rules/preserve-public-entrypoints.md @@ -0,0 +1,19 @@ +# Rule: Preserve public entrypoints + +> Navigation: [`AGENTS.md`](../../AGENTS.md) · [`SPEC_INDEX.md`](../SPEC_INDEX.md). + +## Rule + +Change a package `src/index.js|ts`, package `main`/`module`, widget name, data-toggle name, event string, or public prop/type only with an approved compatibility delta and synchronized contract documentation. + +## Why + +The repository publishes dozens of independently imported packages plus browser/CDN widgets. A seemingly local rename can break npm imports, host mounting, callbacks, or existing markup. + +## How to follow + +Use the exact source entrypoint and owning module spec; update `ai-docs/CONTRACTS.md`, `.sdd/manifest.json`, tests, changelog/migration guidance, and any generated artifact configuration in the same change. + +## Enforced by + +Review checks C1–C3, independent spec validation, Jest/journey tests where available, and release review. There is no single automated API-diff gate today.