diff --git a/.gitignore b/.gitignore index 106dcece0..8d48cdb40 100644 --- a/.gitignore +++ b/.gitignore @@ -41,4 +41,8 @@ reports/ !package.json !package-lock.json !src/data/*.json +!.sdd/manifest.json node_modules/ + +# SDD temporary skill outputs +.generated/ diff --git a/.sdd/manifest.json b/.sdd/manifest.json new file mode 100644 index 000000000..22160feea --- /dev/null +++ b/.sdd/manifest.json @@ -0,0 +1,268 @@ +{ + "manifest_version": 1, + "repository": { + "name": "webex/components", + "purpose": "Published React component library for embedding Webex-styled meeting, messaging, and people UI with adapter-injected data.", + "category": "cat1-legacy", + "primary_languages": ["javascript"] + }, + "topology": "Single-repo", + "commands": { + "install": { + "command": "npx npm-install-peers", + "source_file": "CONTRIBUTING.md" + }, + "build": { + "command": "npm run build", + "source_file": "package.json" + }, + "test": { + "command": "npm run test", + "source_file": "package.json" + }, + "lint": { + "command": "npm run linter", + "source_file": "package.json" + }, + "coverage": { + "command": "npm run test:coverage", + "source_file": "package.json" + }, + "dev": { + "command": "npm run storybook", + "source_file": "package.json" + } + }, + "coverage_status_definitions": { + "specced": ">=80% public surface specced, drift <5% — spec is authoritative", + "partial": "40-80% specced — spec is a hint, cross-check code", + "untracked": "<40% specced — code is the source of truth" + }, + "modules": [ + { + "path": "src/components/", + "coverage_status": "Specced", + "coverage_evidence": "98% field score assessed 2026-07-27; all 31 barrel exports in Public Surface, hooks, HOCs, internal components, and conventions documented", + "canonical_spec": "src/components/ai-docs/components-spec.md", + "contracts": { + "provides": [ + "WebexMeeting, WebexMessaging, and other exported React components", + "withAdapter HOC and WebexDataProvider", + "AdapterContext and meeting hooks" + ], + "requires": [ + "@webex/component-adapter-interfaces adapter instances", + "react, react-dom, prop-types, rxjs peer dependencies", + "compiled CSS from styles-themes module" + ] + }, + "last_assessed": "2026-07-23", + "section_profile": { + "has_ui": true, + "crosses_service_boundaries": false, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": false, + "has_design_tradeoff": false, + "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": "cursor-agent-session (bootstrap questionnaire)", + "resolved_at": "2026-07-23T12:45:00Z" + } + }, + { + "path": "src/adapters/", + "coverage_status": "Specced", + "coverage_evidence": "97% field score assessed 2026-07-23; all domain adapters, meeting controls registry, and conventions documented", + "canonical_spec": "src/adapters/ai-docs/adapters-spec.md", + "contracts": { + "provides": [ + "WebexJSONAdapter façade (npm public export via src/index.js)", + "Internal domain JSON adapters composed by façade: meetings, people, rooms, activities, memberships, organizations" + ], + "requires": [ + "@webex/component-adapter-interfaces", + "rxjs", + "JSON datasource with activities, meetings, memberships, organizations, people, rooms keys" + ] + }, + "last_assessed": "2026-07-23", + "section_profile": { + "has_ui": false, + "crosses_service_boundaries": false, + "enforces_domain_rules": false, + "is_concurrent_async": true, + "owns_persistence": false, + "returns_caller_errors": true, + "has_design_tradeoff": false, + "stateful_transitions": true, + "exposes_wire_protocol": false, + "ui_multi_screen": false, + "large_data_model": false, + "has_tiers": false, + "module_specific_conventions": false, + "published_package": true, + "embedded_in_host": true, + "holds_client_state": true, + "resolved_by": "cursor-agent-session (bootstrap questionnaire)", + "resolved_at": "2026-07-23T12:45:00Z" + } + }, + { + "path": "src/styles/", + "coverage_status": "Specced", + "coverage_evidence": "95% field score assessed 2026-07-23; SCSS registry, themes, fonts, build outputs, and conventions documented", + "canonical_spec": "src/styles/ai-docs/styles-themes-spec.md", + "contracts": { + "provides": [ + "dist/css/webex-components.css", + "dist/themes/dark and dist/themes/light assets", + "dist/assets/fonts" + ], + "requires": [ + "component SCSS partials", + "rollup-plugin-scss and rollup-plugin-copy" + ] + }, + "last_assessed": "2026-07-23", + "section_profile": { + "has_ui": false, + "crosses_service_boundaries": false, + "enforces_domain_rules": false, + "is_concurrent_async": false, + "owns_persistence": false, + "returns_caller_errors": false, + "has_design_tradeoff": false, + "stateful_transitions": false, + "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": false, + "resolved_by": "cursor-agent-session (bootstrap questionnaire)", + "resolved_at": "2026-07-23T12:45:00Z" + } + } + ], + "spec_policy": { + "delta_grammar": { + "added": "## ADDED Requirements", + "modified": "## MODIFIED Requirements", + "removed": "## REMOVED Requirements" + }, + "require_what_and_why": true, + "require_provenance": true, + "protected_specs": [], + "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": "cursor-agent-session", + "generator_model": "composer", + "generator_runtime_source": "host-metadata", + "validator_runtime": "codex-agent-session", + "validator_model": "gpt-5", + "validator_runtime_source": "host-metadata", + "minimum_independence": "different-runtime", + "runtime_fallback_tier": "different-runtime", + "blocking_severities": ["Blocking"], + "generator_run_id": "bootstrap-2026-07-23T124500Z", + "validator_run_id": "validation-2026-07-23T085321Z", + "source_commit": "c0140453d4137b7b78bb5656bd13c3e13215d82b", + "base_ref": "master", + "head_ref": "SDLC_SKILLS_FOR_COMPONENTS", + "status": "pass-with-warnings" + }, + "layout": { + "sdd_root": ".sdd", + "docs_root": "ai-docs", + "standing_docs_root": "ai-docs", + "spec_index_path": "ai-docs/SPEC_INDEX.md", + "module_docs_strategy": "source-local", + "module_docs_folder_name": "ai-docs", + "template_roots": { + "canonical": ".sdd/templates", + "extensions": [".sdd/templates/extensions"] + }, + "repo_skills_root": ".sdd/skills", + "contracts_strategy": "root-index-module-detail" + }, + "tooling": { + "sdlc_skills": { + "source_repo": "git@sqbu-github.cisco.com:WebexDevPlatform/SDLC-Skills.git", + "source_ref": "d5ec17e136514addc76df19760f1f00b05ba9c70", + "install_mode": "copy", + "installed_at": "2026-07-23T12:45:00Z", + "plugins": ["sdd-bootstrap"] + } + }, + "coverage_ratchet": { + "enabled": true, + "waivers": [] + }, + "substrate": { + "source": "repo-standards substrate", + "consumed": [ + "ESLint config", + "husky pre-commit/pre-push hooks", + "commitlint", + "CircleCI pipeline", + "Storybook configuration", + "README.md", + "CONTRIBUTING.md", + "CODEOWNERS" + ], + "compliance_tier": "baseline" + }, + "section_profiles": { + "repo": { + "owns_datastore": false, + "holds_client_state": true, + "components_interact": true, + "domain_data_across_components": true, + "caches_data": false, + "observability_convention": false, + "deploys_to_infra": false, + "shared_base_libs": true, + "is_monorepo": false, + "multi_platform": false, + "published_package": true, + "embedded_in_host": true, + "cross_repo_deps_material": true, + "security_arch_warranted": false + }, + "resolved_by": "cursor-agent-session (bootstrap questionnaire)", + "resolved_at": "2026-07-23T12:45:00Z" + }, + "bootstrap": { + "branch": "SDLC_SKILLS_FOR_COMPONENTS", + "jira": null, + "spec_source_policy_applied": false, + "spec_source_policy_skip_reason": "No existing intent/design specs or AI docs; README and per-component READMEs are reference-only" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..305c6aa39 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,115 @@ + + +# AGENTS.md — webex/components + +> You are the agent entry point — read first. Next: router [`SPEC_INDEX.md`](ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ai-docs/ARCHITECTURE.md). Load this + `SPEC_INDEX.md` first; pull module/standing docs on demand. + +**@webex/components** is a published React component library that embeds Webex-styled meeting, messaging, and people UI into host applications. Data flows through adapter interfaces from `@webex/component-adapter-interfaces`; this repo ships JSON mock adapters for Storybook and local development. + +**What it is:** +- React 18 UI components (meetings, messaging, roster, settings, auth flows) +- JSON adapters implementing Webex adapter interfaces for offline/demo use +- Rollup-built npm package with SCSS themes and bundled CSS + +**What it is NOT:** +- ❌ A full Webex client or SDK — it does not call Webex cloud APIs directly +- ❌ Webex Widgets — widgets bundle the SDK adapter; this library expects hosts to supply adapters +- ❌ A backend service — no server, datastore, or deployment target in this repo + +## Tech Stack + +- JavaScript (ES modules), React 18.3.1, PropTypes, RxJS 6 +- Build: Rollup, Babel, SCSS (`rollup-plugin-scss`) +- Test: Jest, React Testing Library, Storybook 6, Chromatic +- Peer deps: `react`, `react-dom`, `prop-types`, `rxjs`, `@babel/runtime` + +## Architecture + +``` +Host App + └─ withAdapter(Component, adapterFactory) or WebexDataProvider + └─ AdapterContext (meetings, people, rooms, …) + └─ Webex* React components (hooks read adapter observables) +``` + +→ Full repo architecture: **[ARCHITECTURE.md](./ai-docs/ARCHITECTURE.md)** + +## Module / Package Structure + +``` +src/ +├── components/ # Exported + internal React components, hooks, HOCs +├── adapters/ # WebexJSONAdapter + domain JSON adapters +├── styles/ # Global SCSS variables, mixins, defaults +├── themes/ # dark/light theme tokens + assets +├── assets/ # Fonts copied to dist on build +├── constants.js # Class prefix and shared string constants +└── util.js # Shared helpers (deepMerge, rxjs chainWith, …) +``` + +→ Per-module docs: **[ai-docs/SPEC_INDEX.md](./ai-docs/SPEC_INDEX.md)** + +## Critical Rules + +1. **Code is the source of truth.** Never invent an API, prop, adapter method, or export — read `src/index.js` and barrel files. +2. **Ask before coding.** Present a plan / Spec Summary; wait for confirmation. +3. **Adapter boundary.** Components consume data only via `@webex/component-adapter-interfaces` adapters injected through `WebexDataProvider` or `withAdapter`. +4. **Class prefix.** Use `WEBEX_COMPONENTS_CLASS_PREFIX` (`wxc`) from `src/constants.js` — must stay aligned with `src/styles/_variables.scss`. +5. **Peer dependencies.** Do not bundle `react`, `react-dom`, `prop-types`, or `rxjs` — they are Rollup externals. +6. **Tests required.** Changes must include Jest tests; follow existing snapshot and hook test patterns. +7. **Lint clean.** `npm run linter` must pass; avoid disabling ESLint rules without maintainer approval. + +## Essential Commands + +| Task | Command | +|---|---| +| Install (with peers) | `npx install-peerdeps @webex/components` (consumers) / `npx npm-install-peers` (dev) | +| Build | `npm run build` | +| Test | `npm run test` | +| Coverage | `npm run test:coverage` | +| Lint | `npm run linter` | +| Dev / Storybook | `npm run storybook` | + +## Common Gotchas + +- **Styles side effect:** `src/index.js` imports `./styles/index.scss` — consumers must load compiled CSS from `dist/css/webex-components.css` or equivalent. +- **Adapter connect lifecycle:** `withAdapter` renders the wrapped component immediately; while `adapter.connect()` is pending, it returns the component **without** `WebexDataProvider` and passes `adapterConnected={false}`. After connect resolves, it re-renders with `WebexDataProvider` and `adapterConnected={true}`. Do not assume `AdapterContext` is available on first paint. +- **JSON adapter datasource shape:** `WebexJSONAdapter` expects top-level keys `activities`, `meetings`, `memberships`, `organizations`, `people`, `rooms`. +- **Semantic release:** Version bumps are automated — do not manually edit version in `package.json` for releases. + +## Boundaries + +### Always +- Read this file + `ai-docs/SPEC_INDEX.md` before touching code. +- Match existing component folder layout (`ComponentName/ComponentName.jsx`, co-located tests/stories). +- Update the manifest-routed module spec when changing public surface or behavior. + +### Ask first +- New npm dependency or peer dependency change. +- New exported component or breaking prop/adapter contract change. +- Changes to Rollup externals or published `files` list. + +### Never +- Commit secrets, tokens, or credentials. +- Disable tests or lint to force green CI. +- Overwrite canonical specs without `spec-reconcile` approval. + +## Doc Routing + +| Need | Load | +|---|---| +| System shape | `ai-docs/ARCHITECTURE.md` | +| Public exports | `ai-docs/CONTRACTS.md` | +| Module work | `/ai-docs/-spec.md` | +| Setup | `ai-docs/GETTING_STARTED.md` | +| Enforceable rules | `ai-docs/RULES.md` | +| Conventions | `ai-docs/patterns/` | + +Machine contract: `.sdd/manifest.json` diff --git a/ai-docs/ARCHITECTURE.md b/ai-docs/ARCHITECTURE.md new file mode 100644 index 000000000..9f102e3fc --- /dev/null +++ b/ai-docs/ARCHITECTURE.md @@ -0,0 +1,179 @@ + + +# ARCHITECTURE — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md) · router [`SPEC_INDEX.md`](SPEC_INDEX.md). Per-module detail in manifest-routed module specs. + +## Design Overview + +**@webex/components** is a single-package React library (topology: Single-repo) that separates **presentation** (React components under `src/components/`) from **data access** (adapters under `src/adapters/` implementing `@webex/component-adapter-interfaces`). Host applications either wrap components with `withAdapter` and a live SDK adapter factory, or use the bundled JSON adapters for demos and Storybook. + +Rollup produces ES and UMD bundles plus compressed CSS, theme folders, and font assets. The library is published to npm; hosts embed components and supply meeting/messaging data through adapter observables (RxJS). + +## Component Inventory & Responsibilities + +| Component | Responsibility (one line) | Docs | +|---|---|---| +| `src/components/` | Webex-styled React UI, hooks, HOCs, generic primitives | `src/components/ai-docs/components-spec.md` | +| `src/adapters/` | JSON-backed adapter implementations for offline/demo data | `src/adapters/ai-docs/adapters-spec.md` | +| `src/styles/` + `src/themes/` + `src/assets/` | Global SCSS, theme tokens, fonts copied to dist | `src/styles/ai-docs/styles-themes-spec.md` | + +## Component Interaction + +```mermaid +flowchart LR + Host[Host application] + WA[withAdapter HOC] + WDP[WebexDataProvider] + AC[AdapterContext] + Comp[Webex React components] + Hooks[useMeeting / usePerson / …] + Adapter[WebexJSONAdapter or SDK adapter] + IF["@webex/component-adapter-interfaces"] + + Host --> WA + WA --> Adapter + WA --> WDP + WDP --> AC + Comp --> Hooks + Hooks --> AC + Adapter --> IF +``` + +Narrative: Host creates an adapter (JSON datasource object or SDK-backed factory). `withAdapter` instantiates the adapter, calls `connect()`, then wraps output in `WebexDataProvider`. Child components use hooks (`useMeeting`, `usePerson`, etc.) that read from `AdapterContext` and subscribe to adapter observables. Meeting controls delegate to adapter control objects (e.g. `MeetingsJSONAdapter` control classes). + +## Execution & Flow + +**Embed & render flow (library consumer):** + +1. Host installs `@webex/components` and peer dependencies. +2. Host imports components and CSS (`dist/css/webex-components.css`). +3. Host wraps a tree with `withAdapter(MyView, adapterFactory)` or provides `WebexDataProvider`. +4. Component hooks subscribe to adapter streams; UI updates on observable emissions. +5. User actions invoke adapter control methods (join, mute, roster toggle, etc.). + +Evidence: `src/components/hoc/withAdapter.jsx`, `src/components/WebexDataProvider/WebexDataProvider.jsx`, `src/components/hooks/useMeeting.js`. + +## Dependencies + +| Dependency | Type | How used | Failure / version handling | +|---|---|---|---| +| `@webex/component-adapter-interfaces` | external npm | Adapter base classes and meeting state enums | Pinned in `package.json`; alias in Rollup config | +| `react` / `react-dom` | peer | UI rendering | Excluded from bundle; host must supply 18.3.1 | +| `rxjs` | peer | Adapter observables | Excluded from bundle | +| `prop-types` | peer | Runtime prop validation | Excluded from bundle | +| `adaptivecards-templating`, `markdown-it`, etc. | npm dep | Adaptive cards and markdown in messaging UI | Bundled; version pinned in `package.json` | + +### State Model + +- **React local state:** Component-level UI state (modals, layout, dimensions) via hooks. +- **Adapter-driven state:** Meeting membership, media, controls — sourced from adapter observables, not local persistence. +- **Context:** `AdapterContext`, `MeetingContext` propagate adapter and meeting scope. + +Evidence: `src/components/hooks/contexts.js`, `src/adapters/MeetingsJSONAdapter.js`. + +## Domain Data Across Components + +Meeting, people, room, activity, and membership data flows through **adapter observables** — not shared in-repo stores. Components module hooks subscribe; adapters module owns JSON/SDK-shaped state mutations. Styles module has no domain data. + +Evidence: `src/adapters/MeetingsJSONAdapter.js`, `src/components/hooks/useMeeting.js`. + +## Shared Base Libraries + +| Shared asset | Location | Used by | +|---|---|---| +| `src/constants.js` | Class prefix, ARIA strings | components, styles | +| `src/util.js` | deepMerge, rxjs chainWith, URL validation | adapters, components | +| `@webex/component-adapter-interfaces` | Adapter contracts (external) | adapters, components hooks | +| Generic UI (`Button`, `Modal`, …) | `src/components/generic/` | Webex feature components | + +Evidence: `src/index.js`, `src/util.js`, `package.json`. + +## Cross-Cutting Concerns + +- **Security:** Library runs in host browser context; hosts must protect access tokens. Components do not persist credentials. URL validation via `src/util.js` `isValidUrl`. +- **Observability:** No server-side logging; optional `useMetrics` hook for host integration. Storybook/Chromatic for visual regression. +- **Accessibility:** Components use ARIA labels; shared constants for disabled media states in `src/constants.js`. +- **Styling:** BEM-like `wxc-` prefixed classes from SCSS; themes under `src/themes/dark.scss` and `src/themes/light.scss`. + +## Build & Packaging + +Rollup entry `src/index.js` emits: + +- `dist/es/webex-components.es.js` (+ minified) +- `dist/umd/webex-components.umd.js` (+ minified) +- `dist/css/webex-components.css` +- `dist/themes/` (copied from `src/themes/dark`, `src/themes/light`) +- `dist/assets/fonts/` (copied from `src/assets/fonts`) + +Evidence: `rollup.config.js`, `package.json` `files` and `module`/`main` fields. + +## Release & Versioning + +- **Publish target:** npm public registry (`@webex/components`, access public per `package.json`). +- **Versioning:** semantic-release automates version bumps — do not manually edit version for releases. +- **Deprecation:** Beta status per README; breaking changes documented in CHANGELOG. +- **Consumer changelog:** `CHANGELOG.md` maintained by semantic-release. + +Evidence: `package.json`, `README.md`, `.circleci/config.yml`. + +## Host Integration & Theming + +- Host installs peer deps: `react@18.3.1`, `react-dom@18.3.1`, `rxjs`, `prop-types`. +- Host imports `dist/css/webex-components.css` and optionally theme assets from `dist/themes/`. +- Host wraps UI with `withAdapter(Component, factory)` or `WebexDataProvider`. +- Production Webex data: use `@webex/sdk-component-adapter` (external repo) — not bundled here. +- Class prefix `wxc` and theme SCSS must stay aligned across JS and CSS. + +Evidence: `README.md`, `src/components/hoc/withAdapter.jsx`, `package.json` peerDependencies. + +## Cross-Repo Dependency Graph + +- **Consumed (external npm):** `@webex/component-adapter-interfaces` — adapter type contracts. +- **Consumed (external, production hosts):** `@webex/sdk-component-adapter` — live Webex SDK adapter (not in this repo). +- **Related product:** [Webex Widgets](https://github.com/webex/widgets) — higher-level widgets built on components + SDK adapter. +- **Published artifact:** `@webex/components` npm package consumed by host applications and widgets. +- **SDD tooling source:** `SDLC-Skills` plugin installed locally (not a runtime dependency). + +Evidence: `README.md`, `package.json`. + +## Testing & Quality + +- Jest unit/snapshot tests co-located with components and adapters. +- Storybook documents components at https://webex.github.io/components/storybook. +- CircleCI workflow `test_and_storybook` (see `.circleci/config.yml`): + - `install` → `lint` and `unit_test` run in parallel (both require install). + - `storybook_preview` (Chromatic via `npm run chromatic`) runs after `unit_test` on **non-master** branches only. + - `build` runs after both `lint` and `unit_test` on **master** only. + +```mermaid +flowchart TB + install[install] + lint[lint] + unitTest[unit_test] + chromatic[storybook_preview] + build[build] + install --> lint + install --> unitTest + unitTest --> chromatic + lint --> build + unitTest --> build +``` + +Evidence: `.circleci/config.yml`, `package.json` scripts. + +## Architecture Reference Links + +| Reference | Location | When to read | +|---|---|---| +| Repo patterns | `patterns/` | Implementation conventions (withAdapter, class prefix, co-located tests) | +| Enforceable rules | `RULES.md` | Coverage map, testing, and security must-dos | +| Security baseline | `SECURITY.md` | Trust boundaries before auth/token-related UI changes | +| Review checks | `REVIEW_CHECKLIST.md` | Before merge of spec-affecting changes | diff --git a/ai-docs/CONTRACTS.md b/ai-docs/CONTRACTS.md new file mode 100644 index 000000000..6518c21cd --- /dev/null +++ b/ai-docs/CONTRACTS.md @@ -0,0 +1,103 @@ + + +# Contracts Catalog — webex/components + +> Machine source: `.sdd/manifest.json`. Package entry: `src/index.js` (Rollup input). Component barrel: `src/components/index.js`. Adapter module barrel: `src/adapters/index.js` (internal — only `WebexJSONAdapter` is re-exported from the package root). + +### Exported API & Types + +| Contract ID | Owner | Symbol | Purpose | Stability | Defined at | +|---|---|---|---|---|---| +| pkg.WebexJSONAdapter | adapters | WebexJSONAdapter | JSON adapter façade (npm public API) | Semver beta | `src/index.js` | +| pkg.WEBEX_COMPONENTS_CLASS_PREFIX | components | WEBEX_COMPONENTS_CLASS_PREFIX | CSS class prefix `'wxc'` | Stable | `src/index.js` | +| pkg.WebexAvatar | components | WebexAvatar | Avatar display | Semver beta | `src/components/index.js` | +| pkg.WebexActivity | components | WebexActivity | Single activity | Semver beta | `src/components/index.js` | +| pkg.WebexActivityStream | components | WebexActivityStream | Activity stream | Semver beta | `src/components/index.js` | +| pkg.WebexAdaptiveCards | components | WebexAdaptiveCards | Adaptive cards container | Semver beta | `src/components/index.js` | +| pkg.WebexDataProvider | components | WebexDataProvider | Adapter context provider | Semver beta | `src/components/index.js` | +| pkg.WebexInMeeting | components | WebexInMeeting | In-meeting layout | Semver beta | `src/components/index.js` | +| pkg.WebexInterstitialMeeting | components | WebexInterstitialMeeting | Pre-join interstitial | Semver beta | `src/components/index.js` | +| pkg.WebexLocalMedia | components | WebexLocalMedia | Local media display | Semver beta | `src/components/index.js` | +| pkg.WebexMediaAccess | components | WebexMediaAccess | Media permission prompt | Semver beta | `src/components/index.js` | +| pkg.WebexMeeting | components | WebexMeeting | Full meeting shell | Semver beta | `src/components/index.js` | +| pkg.WebexMeetingGuestAuthentication | components | WebexMeetingGuestAuthentication | Guest auth UI | Semver beta | `src/components/index.js` | +| pkg.WebexMeetingHostAuthentication | components | WebexMeetingHostAuthentication | Host auth UI | Semver beta | `src/components/index.js` | +| pkg.WebexMeetingControl | components | WebexMeetingControl | Single meeting control | Semver beta | `src/components/index.js` | +| pkg.WebexMeetingControlBar | components | WebexMeetingControlBar | Control bar | Semver beta | `src/components/index.js` | +| pkg.WebexMeetingInfo | components | WebexMeetingInfo | Meeting info display | Semver beta | `src/components/index.js` | +| pkg.WebexMember | components | WebexMember | Single member row | Semver beta | `src/components/index.js` | +| pkg.WebexMemberRoster | components | WebexMemberRoster | Member roster | Semver beta | `src/components/index.js` | +| pkg.WebexMessaging | components | WebexMessaging | Messaging UI | Semver beta | `src/components/index.js` | +| pkg.WebexRemoteMedia | components | WebexRemoteMedia | Remote media tiles | Semver beta | `src/components/index.js` | +| pkg.WebexSettings | components | WebexSettings | Settings panel | Semver beta | `src/components/index.js` | +| pkg.WebexWaitingForHost | components | WebexWaitingForHost | Waiting state UI | Semver beta | `src/components/index.js` | +| pkg.SignIn | components | SignIn | Sign-in UI | Semver beta | `src/components/index.js` | +| pkg.WebexSearchPeople | components | WebexSearchPeople | People search UI | Semver beta | `src/components/index.js` | +| pkg.WebexCreateSpace | components | WebexCreateSpace | Create space UI | Semver beta | `src/components/index.js` | +| pkg.useMeetingDestination | components | useMeetingDestination | Meeting destination hook | Semver beta | `src/components/index.js` | +| pkg.withMeeting | components | withMeeting | Meeting scope HOC | Semver beta | `src/components/index.js` | +| pkg.withAdapter | components | withAdapter | Adapter lifecycle HOC | Semver beta | `src/components/index.js` | +| pkg.Button | components | Button | Generic button | Semver beta | `src/components/index.js` | +| pkg.Modal | components | Modal | Generic modal | Semver beta | `src/components/index.js` | +| pkg.AdapterContext | components | AdapterContext | React context for adapter | Semver beta | `src/components/index.js` | +| pkg.MeetingContext | components | MeetingContext | React context for meeting scope | Semver beta | `src/components/index.js` | +| pkg.css.bundle | styles-themes | webex-components.css | Compiled stylesheet | Semver beta | `dist/css/webex-components.css` | +| pkg.themes | styles-themes | dist/themes/* | Theme asset folders | Semver beta | `rollup.config.js` | +| pkg.fonts | styles-themes | dist/assets/fonts/* | Font files (CiscoSansTT) | Semver beta | `rollup.config.js` | + +### Internal adapter classes (not npm package exports) + +Composed by `WebexJSONAdapter` and exported from the adapter module barrel for tests/Storybook — **not** re-exported from `src/index.js`. + +| Symbol | Purpose | Defined at | +|---|---|---| +| ActivitiesJSONAdapter | Activities domain | `src/adapters/ActivitiesJSONAdapter.js` | +| MeetingsJSONAdapter | Meetings domain + controls | `src/adapters/MeetingsJSONAdapter.js` | +| MembershipJSONAdapter | Memberships domain | `src/adapters/MembershipJSONAdapter.js` | +| OrganizationsJSONAdapter | Organizations domain | `src/adapters/OrganizationsJSONAdapter.js` | +| PeopleJSONAdapter | People domain | `src/adapters/PeopleJSONAdapter.js` | +| RoomsJSONAdapter | Rooms domain | `src/adapters/RoomsJSONAdapter.js` | + +Evidence: `src/adapters/index.js` vs `src/index.js` (only `WebexJSONAdapter` is published). + +### Internal-only components (not in public barrel) + +| Symbol | Purpose | Defined at | +|---|---|---| +| WebexAdaptiveCard | Single adaptive card renderer | `src/components/WebexAdaptiveCard/` | +| WebexAudioSettings | Audio device settings | `src/components/WebexAudioSettings/` | +| WebexVideoSettings | Video device settings | `src/components/WebexVideoSettings/` | +| WebexMeetingProvider | Meeting context wrapper | `src/components/WebexMeetingProvider/` | +| WebexNoMedia | No media placeholder | `src/components/WebexNoMedia/` | + +Evidence: folder listing under `src/components/` vs `src/components/index.js` exports. + +## Requires — what this repo depends on + +| Dependency (service / package / datastore) | What is consumed | Schema / detail link | Availability assumption | Fallback on failure | Version floor | +|---|---|---|---|---|---| +| `@webex/component-adapter-interfaces` | Adapter base classes, `MeetingState`, domain adapter contracts | npm package + `src/adapters/ai-docs/adapters-spec.md` | Host supplies a connected adapter instance at runtime | Components cannot load Webex data; host must inject SDK or JSON adapter | ^1.28.0 | +| `react` / `react-dom` | UI rendering | `package.json` peerDependencies | Required at install/build time | Build or runtime failure if missing | 18.3.1 | +| `rxjs` | Observable streams from adapters | `package.json` peerDependencies | Required at install/build time | Adapter hooks cannot subscribe | ^6.6.2 | +| `prop-types` | Runtime prop validation | `package.json` peerDependencies | Required at install/build time | Dev-time PropTypes warnings only | ^15.7.2 | +| `@babel/runtime` | Transpiled helper functions | `package.json` peerDependencies | Required at install/build time | Runtime errors in transpiled code | ^7.11.2 | +| `adaptivecards-templating` | Adaptive card template expansion | `package.json` dependencies | Bundled with package | Adaptive card UI degrades or fails render | ^2.2.0 | +| `markdown-it` | Markdown rendering in messaging | `package.json` dependencies | Bundled with package | Messaging markdown falls back to plain text or fails render | ^12.3.2 | +| Host adapter (production) | Live Webex data via `@webex/sdk-component-adapter` (external) | `README.md`, `@webex/component-adapter-interfaces` | Host-managed Webex SDK session | Use `WebexJSONAdapter` for offline/demo only | n/a | + +## Compatibility & Deprecation Policy + +- **Breaking-change rule:** Beta product — breaking changes may occur; follow semantic-release and CHANGELOG. +- **Deprecation:** Mark in JSDoc and CHANGELOG before removal; prefer additive props. + +## Detailed Interface Docs + +- Module specs: `src/components/ai-docs/components-spec.md`, `src/adapters/ai-docs/adapters-spec.md` +- External adapter contracts: `@webex/component-adapter-interfaces` npm package diff --git a/ai-docs/GETTING_STARTED.md b/ai-docs/GETTING_STARTED.md new file mode 100644 index 000000000..daf2b3d6a --- /dev/null +++ b/ai-docs/GETTING_STARTED.md @@ -0,0 +1,59 @@ + + +# Getting Started — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md) · router [`SPEC_INDEX.md`](SPEC_INDEX.md). + +## Prerequisites + +- Node.js LTS (`lts/iron` per `.nvmrc`; CI uses Node 20.x) +- npm +- For development: peer deps installed via `npx npm-install-peers` + +## Clone & Install + +```bash +git clone git@github.com:webex/components.git +cd components +npx npm-install-peers +``` + +Evidence: `CONTRIBUTING.md`, `.nvmrc`. + +## Build / Run / Test + +| Task | Command | +|---|---| +| Build | `npm run build` | +| Run (local) | `npm run storybook` | +| Test | `npm run test` | +| Coverage | `npm run test:coverage` | +| Lint | `npm run linter` | + +Evidence: `package.json` scripts. + +## First-Run Verification + +- `npm run linter` exits 0 +- `npm run test` passes +- `npm run storybook` serves Storybook on port 6006 +- `npm run build` produces `dist/es/`, `dist/umd/`, `dist/css/` + +## Configuration & Secrets + +- No `.env` required for local component development with JSON adapters. +- Host applications using live Webex data supply their own tokens outside this repo. + +## Where to Go Next + +- Agent entry: `../AGENTS.md` +- System shape: `ARCHITECTURE.md` +- Routing: `SPEC_INDEX.md` +- Module specs: `src/components/ai-docs/components-spec.md`, `src/adapters/ai-docs/adapters-spec.md` diff --git a/ai-docs/GLOSSARY.md b/ai-docs/GLOSSARY.md new file mode 100644 index 000000000..b1ceee5a5 --- /dev/null +++ b/ai-docs/GLOSSARY.md @@ -0,0 +1,40 @@ + + +# Glossary — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md) · router [`SPEC_INDEX.md`](SPEC_INDEX.md). + +## Domain Terms + +| Term | Definition | Authoritative location | Notes / synonyms to avoid | +|---|---|---|---| +| Adapter | Object implementing `@webex/component-adapter-interfaces` that supplies observable Webex domain data to components | `src/adapters/WebexJSONAdapter.js` | Not "provider" (use `WebexDataProvider` for React context only) | +| WebexJSONAdapter | Façade JSON adapter wiring six domain adapters from a static datasource object | `src/adapters/WebexJSONAdapter.js` | Demo/Storybook adapter, not SDK adapter | +| withAdapter | HOC that creates adapter, calls connect/disconnect, wraps tree in `WebexDataProvider` | `src/components/hoc/withAdapter.jsx` | | +| WebexDataProvider | React context provider exposing adapter to descendant hooks | `src/components/WebexDataProvider/WebexDataProvider.jsx` | | +| Meeting control | Imperative action object (join, mute, share, …) exposed by meetings adapter | `src/adapters/MeetingsJSONAdapter/controls/` | Control ID strings in `MeetingsJSONAdapter.js` | +| wxc | CSS class prefix for all Webex component styles | `src/constants.js`, `src/styles/_variables.scss` | Must stay synchronized | +| Component barrel | Public export surface for npm consumers | `src/components/index.js` | | +| Storybook | Interactive component catalog for development/demo | `.storybook/`, `src/**/*.stories.js` | | +| Peer dependency | React/RxJS packages excluded from Rollup bundle | `rollup.config.js` `external` | Host must install | + +## Abbreviations & Acronyms + +| Abbreviation | Expansion | Meaning in this repo | +|---|---|---| +| HOC | Higher-Order Component | `withAdapter`, `withMeeting` | +| UMD | Universal Module Definition | Browser global bundle format | +| ES | ECMAScript modules | `dist/es/` output | +| SCSS | Sassy CSS | Style source format | +| ARIA | Accessible Rich Internet Applications | Accessibility attributes on media controls | + +## Maintenance + +- New exported symbol or domain concept → add term here in the same PR as the code change. diff --git a/ai-docs/REVIEW_CHECKLIST.md b/ai-docs/REVIEW_CHECKLIST.md new file mode 100644 index 000000000..0ca35cea3 --- /dev/null +++ b/ai-docs/REVIEW_CHECKLIST.md @@ -0,0 +1,55 @@ + + +# Review-Check Catalog — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md) (agent entry) · router [`SPEC_INDEX.md`](SPEC_INDEX.md) · system [`ARCHITECTURE.md`](ARCHITECTURE.md). Then this doc at Review & Merge. +> Context-efficiency: link to canonical docs — don't duplicate them; load on demand, not upfront. + +> Each finding records: severity (Blocking / Important / Medium / Minor), check id, file path, what's wrong, +> why it matters, 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 | Spec/docs changed in the same change as code; the implementation plan's repo-specific AI Docs Impact matrix entries are complete and closed; every requirement (incl. ADDED) states WHAT and WHY | Blocking | +| C2 | Contract correctness | Provides/Requires delta is real and complete; no undocumented breaking change to a public surface | Blocking | +| C3 | Code-vs-spec match | Signatures, data-flow, and architecture claims in the spec match the actual code (file path) | Blocking | +| C4 | Test adequacy | Each acceptance criterion has a test with a positive AND a negative case; changed-line coverage meets the bar | Important | +| C5 | Error handling + input validation | Untrusted input validated at boundaries; failure/edge paths handled, not swallowed | Important | +| C6 | Security baseline | No hardcoded secrets; authz enforced; data-classification/logging rules respected (per `SECURITY.md`) | Blocking | + +## Coverage-conditional checks (run by the touched module's manifest coverage state) + +| # | Check | When it applies | What it verifies | Severity | +|---|---|---|---|---| +| K1 | Regression guard | Modifying a weakly covered module, or any MODIFIED/REMOVED requirement | A characterization baseline exists; invariants the change claims NOT to alter still hold (positive + negative) | Blocking | +| K2 | Grounding | Weakly covered module | Claims cite real code (file path), not memory; uncovered public surfaces flagged `[NEEDS HUMAN INPUT]` | Important | +| K3 | Drift threshold | Any tracked module | Module drift is within its status threshold (see `RULES.md` / `coverage-policy.defaults.yaml`) | Important | +| K4 | Coverage-state accuracy | Coverage-state change proposed | The recorded manifest coverage state matches the evidence; promotion/demotion rules honored | Medium | + +## Cross-cutting checks (apply at higher risk / autonomy) + +| # | Check | What it verifies | Severity | +|---|---|---|---| +| X1 | Cross-model review | The artifact was validated by a different runtime than the one that generated it (generator ≠ validator) | Blocking when required | +| X2 | Observability | Logs/metrics/alerts adequate for the change; nothing sensitive logged | Medium | +| X3 | Rollout safety | Feature-flag default is safe; rollback path exists; migration/rollout interlock is correct | Important | + +## How the set is selected + +1. Always run the 6 core checks. +2. Add the coverage-conditional checks whose "when it applies" matches the touched modules' manifest coverage state. +3. Add the cross-cutting checks when the change is high-risk or runs at higher autonomy. + +## Output + +- A compliance matrix + severity-sorted findings + a verdict (Pass / Pass-with-warnings / Blocked). + Draft only; a human posts. diff --git a/ai-docs/RULES.md b/ai-docs/RULES.md new file mode 100644 index 000000000..bcd67a924 --- /dev/null +++ b/ai-docs/RULES.md @@ -0,0 +1,76 @@ + + +# Rules — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md) · critical rules live in AGENTS.md. + +## Coverage Map (which docs/specs to trust) + +| Module | Manifest coverage state | What it means here | +|---|---|---| +| `src/components/` | Specced | Spec is authoritative for exports and patterns | +| `src/adapters/` | Specced | Spec is authoritative for JSON adapter behavior | +| `src/styles/` | Specced | Spec is authoritative for SCSS/build outputs | + +## Autonomy & Ask-First + +- **May proceed:** Internal component refactors that do not change exported props or public exports; test fixes; Storybook-only changes. +- **Ask first:** New exported component; adapter interface behavior change; dependency or peer dependency bump; Rollup config change. +- **Never without explicit human approval:** Publish/release; disable CI checks; overwrite canonical specs. + +## Naming + +- Components: `Webex*` prefix for product components; PascalCase folders matching component name. +- Adapters: `*JSONAdapter` suffix for JSON implementations. +- CSS classes: `wxc-` prefix via `WEBEX_COMPONENTS_CLASS_PREFIX`. +- Tests: co-located `*.test.js` / `*.test.jsx` beside source. + +Evidence: `src/components/`, `src/adapters/`, `src/constants.js`. + +## Logging + +- No centralized server logging in this client library. +- Avoid logging access tokens or PII in host integrations. + +## Error Handling + +- Adapter methods return Promises; `withAdapter` awaits `connect()` before providing context. +- Components guard missing adapter state via hooks and conditional render paths. +- URL inputs validated with `isValidUrl` where used. + +Evidence: `src/components/hoc/withAdapter.jsx`, `src/util.js`. + +## Imports / Dependencies + +- ESLint enforces import rules; run `npm run linter`. +- Do not import server-only or Node builtins in browser components (Rollup `browser: true`). +- New runtime dependencies require maintainer review per CONTRIBUTING. + +## Testing + +- All code changes require tests per CONTRIBUTING (unit at minimum). +- Use Jest snapshots where established; update snapshots deliberately with `-u` in CI context. +- Positive and negative cases for observable adapter behavior. + +Evidence: `CONTRIBUTING.md`, `src/**/*.test.js`. + +## Security + +- Never commit tokens; hosts manage Webex credentials. +- Validate URLs and user-provided strings at component boundaries where applicable. + +## Spec-Currency & Drift Thresholds + +- Update module spec in the same PR as behavior or export changes. +- Partial modules: treat >15% undocumented public surface as drift to backfill. + +## Secrets Policy + +- No secrets in source; `.env*` gitignored except `.env.default` if present. diff --git a/ai-docs/SECURITY.md b/ai-docs/SECURITY.md new file mode 100644 index 000000000..2b09ba9fa --- /dev/null +++ b/ai-docs/SECURITY.md @@ -0,0 +1,65 @@ + + +# Security Baseline — webex/components + +> Client-side React library — hosts own authentication and token storage. + +## Trust Boundaries + +| Boundary | Untrusted side | Trusted side | What is enforced at the crossing | +|---|---|---|---| +| Host application → components | Host props, URLs, user input | Component render tree | PropTypes validation; `isValidUrl` for URL props | +| Components → adapter | User actions (join, mute, …) | Adapter control methods | Adapter interface contract; no direct API calls in this repo | +| npm consumer → package | Imported JS/CSS bundles | Host build pipeline | Peer dependency versions; no secrets in published package | + +## Authentication & Authorization Model + +- **Authentication:** Not implemented in this library. Host applications authenticate to Webex and pass tokens to SDK adapters (external). JSON adapters use static demo data only. +- **Authorization:** Not applicable at library level; meeting join/auth UI components delegate to adapter state. +- **Default posture:** Components render UI only; no privilege decisions without adapter data. + +Evidence: `README.md`, `src/adapters/WebexJSONAdapter.js`. + +## Secret & Credential Handling + +- Secrets source: Host application / SDK adapter (outside this repo). +- Injection: Host passes credentials to adapter factory in `withAdapter` — never commit tokens in this repository. +- Rotation: Host responsibility. +- **Hard rule:** never commit secrets, tokens, keys, or connection strings; never log them in component code. + +Evidence: `.gitignore` (`.env*`), `CONTRIBUTING.md`. + +## Data Classification & Handling + +| Data class | Examples | Storage rule | Logging rule | In transit | +|---|---|---|---|---| +| Host credentials | Webex access tokens | Not stored by library | Never log | HTTPS (host responsibility) | +| Demo JSON | People emails, display names in fixtures | Static JSON in repo for tests/demo | Avoid logging in production hosts | N/A for JSON adapter | +| Media streams | Camera/mic via browser APIs | Browser memory only | No persistence in library | Browser WebRTC (host/adapter) | + +## Input Validation & Output Encoding Posture + +- Validate URLs with `isValidUrl` and explicit protocol allow-lists where used. +- Markdown rendering in messaging uses `markdown-it` — hosts should sanitize untrusted content before display if sourcing external messages. +- Adaptive cards render templated JSON — validate card payload at adapter/host layer. + +Evidence: `src/util.js`, `package.json` dependencies. + +## Known Sensitive Areas & Accepted Risks + +| Area | Risk | Mitigation / why accepted | Owner | +|---|---|---|---| +| Beta API surface | Breaking changes | Documented in README; semver via semantic-release | Maintainers | +| Host token handling | Token exposure in host app | Out of scope; documented in SECURITY | Host integrators | + +## Reporting & Review + +- Report security issues via Webex open-source support channels listed in `README.md`. +- Security-sensitive changes require maintainer review per `CONTRIBUTING.md`. diff --git a/ai-docs/SPEC_INDEX.md b/ai-docs/SPEC_INDEX.md new file mode 100644 index 000000000..9e4e0a823 --- /dev/null +++ b/ai-docs/SPEC_INDEX.md @@ -0,0 +1,50 @@ + + +# Spec Index — webex/components + +> Start here → root [`AGENTS.md`](../AGENTS.md). **Source of truth:** `.sdd/manifest.json` (this file mirrors it). + +## Module Registry + +| Module | Responsibility | Manifest coverage state | Start here | +|---|---|---|---| +| `src/components/` | React UI, hooks, HOCs, generic widgets | Specced | `src/components/ai-docs/components-spec.md` | +| `src/adapters/` | JSON adapter implementations | Specced | `src/adapters/ai-docs/adapters-spec.md` | +| `src/styles/` | SCSS, themes, fonts | Specced | `src/styles/ai-docs/styles-themes-spec.md` | + +## Task Routing + +| If the task is… | Load | +|---|---| +| Understanding the system | `ARCHITECTURE.md` | +| Working in components | `src/components/ai-docs/components-spec.md` | +| Working in adapters | `src/adapters/ai-docs/adapters-spec.md` | +| Styling / themes | `src/styles/ai-docs/styles-themes-spec.md` | +| Public export change | `CONTRACTS.md` + affected module spec | +| Updating docs after code change | affected module spec + `SPEC_INDEX.md` | + +## Incident History + +| INC id | Date | Module | One-line | Link | +|---|---|---|---|---| +| — | — | — | No incident rows recorded at bootstrap | — | + +## Spec Registry + +| Doc | Location | Purpose | +|---|---|---| +| Architecture | `ARCHITECTURE.md` | System components and interaction | +| Contracts | `CONTRACTS.md` | Public npm export catalog | +| Getting started | `GETTING_STARTED.md` | Clone, build, test, Storybook | +| Rules | `RULES.md` | Enforceable conventions beyond AGENTS | +| Glossary | `GLOSSARY.md` | Domain terms and code locations | +| Security | `SECURITY.md` | Client-side trust boundaries and secret handling | +| Review catalog | `REVIEW_CHECKLIST.md` | SDD review checks | +| Patterns | `patterns/` | Co-located tests, wxc prefix, withAdapter injection | diff --git a/ai-docs/patterns/co-located-tests.md b/ai-docs/patterns/co-located-tests.md new file mode 100644 index 000000000..1803c30f3 --- /dev/null +++ b/ai-docs/patterns/co-located-tests.md @@ -0,0 +1,44 @@ + + +# Pattern: co-located component tests + +> Router [`SPEC_INDEX.md`](../SPEC_INDEX.md) · [`RULES.md`](../RULES.md) + +## When to use + +When adding or modifying a React component under `src/components/`. + +## Correct + +```javascript +// from src/components/WebexMeeting/WebexMeeting.test.js +// Test file beside component; mock adapter context; snapshot or behavior assertions +``` + +Place `ComponentName.test.js` or `ComponentName.test.jsx` in the same folder as `ComponentName.jsx`. + +## Incorrect + +```javascript +// Centralized tests/ folder only, disconnected from component folder +``` + +**Why wrong:** This repo convention co-locates tests with source for discoverability and CONTRIBUTING compliance. + +## Where it appears + +- `src/components/WebexMeeting/WebexMeeting.test.js` +- `src/components/WebexDataProvider/WebexDataProvider.test.js` +- `src/components/hoc/withAdapter.test.jsx` +- `src/adapters/MeetingsJSONAdapter.test.js` + +## Edge cases / exceptions + +Storybook stories (`.stories.js`) supplement but do not replace unit tests per `CONTRIBUTING.md`. diff --git a/ai-docs/patterns/with-adapter-injection.md b/ai-docs/patterns/with-adapter-injection.md new file mode 100644 index 000000000..5adc131a9 --- /dev/null +++ b/ai-docs/patterns/with-adapter-injection.md @@ -0,0 +1,52 @@ + + +# Pattern: adapter injection via withAdapter + +> Router [`SPEC_INDEX.md`](../SPEC_INDEX.md) · components spec + +## When to use + +When a host application embeds Webex components that need live or demo Webex data. + +## Correct + +```javascript +// from src/components/hoc/withAdapter.jsx +// 1. adapterFactory(props) returns adapter instance +// 2. await adapter.connect() +// 3. wrap with WebexDataProvider when connected +const Enhanced = withAdapter(MyComponent, adapterFactory); +``` + +## Incorrect + +```javascript +// Render WebexMeeting without withAdapter/WebexDataProvider — no AdapterContext + + +// Or: read AdapterContext inside withAdapter-wrapped component without checking adapterConnected +function MyView() { + const adapter = useContext(AdapterContext); // undefined on first paint + return adapter.meetingsAdapter.getMeetingInfo(...); +} +``` + +**Why wrong:** Hooks and context consumers expect `AdapterContext` from `WebexDataProvider`. `withAdapter` renders the wrapped component before connect completes; without the provider (or without checking `adapterConnected`), adapter access fails. + +## Where it appears + +- `src/components/hoc/withAdapter.jsx` +- `src/components/hoc/withAdapter.test.jsx` +- `src/components/WebexDataProvider/WebexDataProvider.jsx` +- Storybook stories using `WebexJSONAdapter` + +## Edge cases / exceptions + +Manual `WebexDataProvider` usage is valid when host manages connect/disconnect lifecycle itself. diff --git a/ai-docs/patterns/wxc-class-prefix.md b/ai-docs/patterns/wxc-class-prefix.md new file mode 100644 index 000000000..0edc8054d --- /dev/null +++ b/ai-docs/patterns/wxc-class-prefix.md @@ -0,0 +1,49 @@ + + +# Pattern: wxc class prefix + +> Router [`SPEC_INDEX.md`](../SPEC_INDEX.md) · [`GLOSSARY.md`](../GLOSSARY.md) + +## When to use + +When adding CSS classes in SCSS or className strings in JavaScript components. + +## Correct + +```javascript +// from src/constants.js +export const WEBEX_COMPONENTS_CLASS_PREFIX = 'wxc'; +``` + +```scss +// from src/styles/_variables.scss +$WEBEX_COMPONENTS_CLASS_PREFIX: 'wxc'; +``` + +Use `webexComponentClasses()` from `src/components/helpers.js` for BEM-style blocks. + +## Incorrect + +```javascript +const prefix = 'webex'; // hardcoded divergent prefix +``` + +**Why wrong:** Breaks theme/CSS alignment and snapshot tests expecting `wxc-` classes. + +## Where it appears + +- `src/constants.js` +- `src/styles/_variables.scss` +- `src/components/helpers.js` +- Multiple component SCSS files under `src/components/` + +## Edge cases / exceptions + +None — prefix must stay synchronized across JS and SCSS. diff --git a/src/adapters/ai-docs/adapters-spec.md b/src/adapters/ai-docs/adapters-spec.md new file mode 100644 index 000000000..c11e6bd0e --- /dev/null +++ b/src/adapters/ai-docs/adapters-spec.md @@ -0,0 +1,329 @@ + + +# adapters — SPEC + +> Start here → root [`AGENTS.md`](../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | adapters | +| Source path(s) | `src/adapters/` | +| Doc kind | Module spec | +| Coverage score | 97% assessed 2026-07-23 — all domain adapters and meeting controls documented | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | cursor-agent-session / pending PR approval / 2026-07-23 | +| Validation status | pass-with-warnings, validator codex-agent-session, assessed 2026-07-23 (0 blocking) | + +## Evidence Rules + +Requirements cite adapter source and co-located `*.test.js` files. Observable behavior validated against `@webex/component-adapter-interfaces` types. + +## Source Material Register + +| Source material | Scope | Decision | Detail location | +|---|---|---|---| +| `@webex/component-adapter-interfaces` | adapter API | reference-only | Requires section; npm package | +| Datasource modules | demo data | verified | `src/data/*.js` (`activities.js`, `meetings.js`, …) | + +## Overview + +The **adapters** module provides JSON-backed implementations of Webex component adapter interfaces for offline development, Storybook, and tests. `WebexJSONAdapter` is the façade that wires domain-specific JSON adapters (meetings, people, rooms, activities, memberships, organizations). + +`MeetingsJSONAdapter` is the largest adapter — it models meeting state, media streams, roster, settings, and meeting controls as RxJS observables with control class instances. + +## Purpose / Responsibility + +Supply in-memory/Webex-shaped data and control semantics to React components without a live Webex SDK connection. Does **not** implement network I/O or token management. + +## Stack + +- JavaScript ES modules +- RxJS (`Observable`, operators) for reactive meeting/people streams +- Extends classes from `@webex/component-adapter-interfaces` +- Jest tests co-located with adapters + +## Folder / Package Structure + +``` +src/adapters/ +├── WebexJSONAdapter.js # Root JSON adapter façade +├── MeetingsJSONAdapter.js # Meetings domain + controls +├── MeetingsJSONAdapter/controls/ # Join, mute, share, roster, … controls +├── PeopleJSONAdapter.js +├── RoomsJSONAdapter.js +├── ActivitiesJSONAdapter.js +├── MembershipJSONAdapter.js +├── OrganizationsJSONAdapter.js +├── index.js # Module barrel (internal; not package root) +└── *.test.js # Domain adapter tests +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `src/adapters/WebexJSONAdapter.js` | Façade wiring datasource keys to domain adapters | +| `src/adapters/MeetingsJSONAdapter.js` | Meeting state machine, observables, control registry | +| `src/adapters/MeetingsJSONAdapter/controls/index.js` | Control exports | +| `src/adapters/index.js` | Adapter module barrel (internal); only `WebexJSONAdapter` is npm-exported via `src/index.js` | +| `src/util.js` | `deepMerge` used by MeetingsJSONAdapter | + +## Public Surface + +Only `WebexJSONAdapter` is published from `@webex/components` (`src/index.js` → Rollup package entry). Domain adapters are internal implementation composed by the façade. + +| Contract ID | Type | Surface | Purpose | Compatibility | Detail link | Root index | +|---|---|---|---|---|---|---| +| adapters.WebexJSONAdapter | npm package export | class | Entry JSON adapter façade | Semver | `src/adapters/WebexJSONAdapter.js` | `ai-docs/CONTRACTS.md` | + +**Internal adapter-barrel exports (not npm package API)** — exported from `src/adapters/index.js` for module tests and Storybook; accessed in apps via `WebexJSONAdapter` domain properties (`meetingsAdapter`, `peopleAdapter`, …): + +| Contract ID | Type | Surface | Purpose | Detail link | +|---|---|---|---|---| +| adapters.MeetingsJSONAdapter | internal | class | Meeting observables + controls | `src/adapters/MeetingsJSONAdapter.js` | +| adapters.PeopleJSONAdapter | internal | class | People search/display data | `src/adapters/PeopleJSONAdapter.js` | +| adapters.RoomsJSONAdapter | internal | class | Space/room data | `src/adapters/RoomsJSONAdapter.js` | +| adapters.ActivitiesJSONAdapter | internal | class | Activity stream data | `src/adapters/ActivitiesJSONAdapter.js` | +| adapters.MembershipJSONAdapter | internal | class | Membership/roster data | `src/adapters/MembershipJSONAdapter.js` | +| adapters.OrganizationsJSONAdapter | internal | class | Organization data | `src/adapters/OrganizationsJSONAdapter.js` | + +## Requires (dependencies) + +- `@webex/component-adapter-interfaces` — base adapter classes, `MeetingState`, control interfaces +- `rxjs` — observables and operators +- `src/util.js` — `deepMerge` for meeting state updates +- Datasource object with keys: `activities`, `meetings`, `memberships`, `organizations`, `people`, `rooms` + +Evidence: `src/adapters/WebexJSONAdapter.js` constructor. + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test Evidence | Gaps | Confidence | +|---|---|---|---|---|---|---| +| ADP-R-001 | `WebexJSONAdapter` MUST extend `WebexAdapter` and construct six domain adapters | Single entry point for JSON demo data | `src/adapters/WebexJSONAdapter.js` | adapter integration tests | none | PRESENT | +| ADP-R-002 | `connect()` / `disconnect()` MUST resolve immediately for JSON adapters | No async network | `src/adapters/WebexJSONAdapter.js` | — | none | PRESENT | +| ADP-R-003 | `MeetingsJSONAdapter` MUST expose meeting control constants (join, mute, share, …) | UI maps controls by ID | `src/adapters/MeetingsJSONAdapter.js` | `src/adapters/MeetingsJSONAdapter.test.js` | none | PRESENT | +| ADP-R-004 | Meeting state updates MUST use RxJS observables | Components subscribe via hooks | `src/adapters/MeetingsJSONAdapter.js` | MeetingsJSONAdapter tests | none | PRESENT | +| ADP-R-005 | Each registered meeting control MUST map to a control class instance in `meetingControls` | Components invoke controls by ID | `src/adapters/MeetingsJSONAdapter.js`, `src/adapters/MeetingsJSONAdapter/controls/` | `MeetingsJSONAdapter.test.js` | `DISABLED_JOIN_CONTROL` defined but not registered | PRESENT | +| ADP-R-006 | Datasource MUST provide keys: activities, meetings, memberships, organizations, people, rooms | WebexJSONAdapter constructor contract | `src/adapters/WebexJSONAdapter.js` | adapter tests | none | PRESENT | +| ADP-R-007 | Each domain adapter MUST extend matching interface adapter class | Interface compliance | `src/adapters/PeopleJSONAdapter.js` | PeopleJSONAdapter.test.js | none | PRESENT | + +## Design Overview + +JSON adapters mirror the SDK adapter shape so components do not branch on data source. `WebexJSONAdapter` delegates to focused adapters per domain. `MeetingsJSONAdapter` centralizes meeting lifecycle (NOT_JOINED → joined states), media permissions, roster visibility, and settings preview — emitting updates through observables consumed by component hooks. + +Control objects encapsulate imperative actions (join, leave, mute audio/video, share screen, device switching) and mutate in-memory meeting state. + +## Data Flow + +```mermaid +flowchart LR + DS[JSON datasource object] + WJA[WebexJSONAdapter] + MJA[MeetingsJSONAdapter] + Ctrl[Control instances] + Obs[RxJS Observables] + Hooks[Component hooks] + + DS --> WJA + WJA --> MJA + MJA --> Obs + Hooks -->|subscribe| Obs + Hooks -->|invoke| Ctrl + Ctrl -->|mutate state| MJA + MJA --> Obs +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| JSON adapter connect + join control | below | invalid meeting ID handled per control tests | + +```mermaid +sequenceDiagram + participant Story as Storybook/Host + participant WJA as WebexJSONAdapter + participant MJA as MeetingsJSONAdapter + participant Join as JoinControl + participant Hook as useMeeting + + Story->>WJA: new WebexJSONAdapter(datasource) + Story->>WJA: connect() + Hook->>MJA: subscribe meetingInfo$ + Hook->>Join: execute join action + Join->>MJA: update meeting status + MJA-->>Hook: observable emission +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class WebexAdapter { + <> + } + class WebexJSONAdapter { + +activitiesAdapter + +meetingsAdapter + +peopleAdapter + +connect() + } + class MeetingsJSONAdapter { + +getMeetingInfo() + +meetingControls() + } + class JoinControl { + +execute() + } + WebexAdapter <|-- WebexJSONAdapter + WebexJSONAdapter --> MeetingsJSONAdapter + MeetingsJSONAdapter --> JoinControl +``` + +## Use Cases + +| UC | Description | Evidence | +|---|---|---| +| UC-1 Storybook demo | Instantiate `WebexJSONAdapter` with static JSON from `src/data/*.js` | `.storybook/` stories | +| UC-2 Unit test | Inject JSON adapter into `WebexDataProvider` | component tests | +| UC-3 Control interaction | User clicks join → `JoinControl` updates state | `MeetingsJSONAdapter/controls/JoinControl.js` | + +## State Model + +In-memory meeting and domain records live on the JSON datasource object passed to `WebexJSONAdapter`. `MeetingsJSONAdapter` mutates meeting entries via `deepMerge` when controls fire; other domain adapters read/write their respective datasource slices. + +| State store | Owner | Notes | +|---|---|---| +| Datasource object | host/test/Storybook | Keys: activities, meetings, memberships, organizations, people, rooms | +| Meeting entries | MeetingsJSONAdapter | Status, media flags, roster visibility updated by controls | +| Observable caches | per adapter method | RxJS streams emit on mutation | + +Evidence: `src/adapters/WebexJSONAdapter.js`, `src/adapters/MeetingsJSONAdapter.js`, `src/util.js`. + +## Concurrency & Reactive Flow + +Meeting updates propagate via RxJS `Observable` streams. Multiple subscribers (hooks) share hot/cold patterns as implemented per adapter method — components must unsubscribe on unmount (handled in hooks). + +Evidence: `src/adapters/MeetingsJSONAdapter.js`, rxjs imports. + +## Error Handling & Failure Modes + +JSON adapters surface invalid inputs and missing records through RxJS `observer.error(...)` on subscribed observables. Callers (hooks, tests, Storybook) MUST handle error emissions — errors are not swallowed or converted to silent empty results. + +| Condition | Signal (error/code/result) | Caller recovery | +|---|---|---| +| Unknown meeting ID | `observer.error(Error('Could not find meeting with ID "…"'))` | Subscribe with error handler; show fallback UI | +| Unknown person ID or search miss | `observer.error(Error('Could not find person…'))` | Guard UI before subscribe; handle error in hook | +| Unknown room ID | `observer.error(Error('Could not find room with ID "…"'))` | Show not-found state; avoid assuming room exists | +| Unknown activity ID | `observer.error(Error('Could not find activity with ID "…"'))` | Handle in activity stream subscription | +| Unknown organization ID | `observer.error(Error('Could not find any organization with ID "…"'))` | Handle in organization lookup | +| Invalid membership destination | `observer.error(Error('Could not find members for destination "…"'))` | Validate destination before subscribe | +| Room creation failure | `observer.error(Error('error in creating room'))` | Retry or surface error to host | +| Control preconditions not met (switch camera/mic/speaker) | `observer.error(Error('Could not find meeting with ID "…" to add … control'))` | Guard control UI until meeting exists | +| Activity attachment/post failure | `observer.error(Error('Unable to create/post…'))` | Show error toast; do not assume success | + +Evidence: `src/adapters/MeetingsJSONAdapter.js`, `src/adapters/PeopleJSONAdapter.js`, `src/adapters/RoomsJSONAdapter.js`, `src/adapters/ActivitiesJSONAdapter.js`, `src/adapters/MembershipJSONAdapter.js`, `src/adapters/OrganizationsJSONAdapter.js`, `src/adapters/MeetingsJSONAdapter/controls/*.js`. + +## State Machine + +Meeting status transitions include `NOT_JOINED` and joined/in-meeting states aligned with `MeetingState` from `@webex/component-adapter-interfaces`. Controls gate transitions (e.g. join, leave). + +Evidence: `src/adapters/MeetingsJSONAdapter.js` `EMPTY_MEETING`, status field. + +## Meeting Controls Registry + +Registered controls instantiated in `meetingControls`: + +| Control constant | Control class | Purpose | +|---|---|---| +| `JOIN_CONTROL` | `JoinControl` | Join meeting | +| `LEAVE_CONTROL` | `LeaveControl` | Leave meeting | +| `MUTE_AUDIO_CONTROL` | `MuteAudioControl` | Toggle audio mute | +| `MUTE_VIDEO_CONTROL` | `MuteVideoControl` | Toggle video mute | +| `SHARE_CONTROL` | `ShareControl` | Screen share | +| `ROSTER_CONTROL` | `RosterControl` | Toggle roster | +| `SETTINGS_CONTROL` | `SettingsControl` | Open settings | +| `SWITCH_CAMERA_CONTROL` | `SwitchCameraControl` | Switch camera device | +| `SWITCH_MICROPHONE_CONTROL` | `SwitchMicrophoneControl` | Switch microphone | +| `SWITCH_SPEAKER_CONTROL` | `SwitchSpeakerControl` | Switch speaker | +| `DISABLED_MUTE_AUDIO_CONTROL` | `DisabledMuteAudioControl` | Disabled mute state | + +**Defined constants not registered in `meetingControls`:** + +| Constant | ID string | Status | +|---|---|---| +| `DISABLED_JOIN_CONTROL` | `disabled-join-meeting` | Exported constant only — no control class and not in `supportedControls()` | + +Evidence: `src/adapters/MeetingsJSONAdapter.js`, `src/adapters/MeetingsJSONAdapter/controls/`, `src/adapters/MeetingsJSONAdapter.test.js`. + +## Domain Adapter Summary + +| Adapter | Source file | Test file | +|---|---|---| +| ActivitiesJSONAdapter | `src/adapters/ActivitiesJSONAdapter.js` | `ActivitiesJSONAdapter.test.js` | +| MeetingsJSONAdapter | `src/adapters/MeetingsJSONAdapter.js` | `MeetingsJSONAdapter.test.js` | +| MembershipJSONAdapter | `src/adapters/MembershipJSONAdapter.js` | `MembershipJSONAdapter.test.js` | +| OrganizationsJSONAdapter | `src/adapters/OrganizationsJSONAdapter.js` | `OrganizationsJSONAdapter.test.js` | +| PeopleJSONAdapter | `src/adapters/PeopleJSONAdapter.js` | `PeopleJSONAdapter.test.js` | +| RoomsJSONAdapter | `src/adapters/RoomsJSONAdapter.js` | `RoomsJSONAdapter.test.js` | + +## Pitfalls + +- Datasource must include all keys expected by `WebexJSONAdapter` constructor. +- Meeting control IDs are string constants — must stay aligned with component control bar mapping. +- `deepMerge` mutates destination objects — callers must pass clones if immutability required. +- Do not treat domain adapter classes as npm public API — only `WebexJSONAdapter` is published from `src/index.js`. + +Evidence: `src/adapters/WebexJSONAdapter.js`, `src/util.js`, `src/index.js`. + +## Export Stability + +Only `WebexJSONAdapter` is a semver-managed npm export from `@webex/components`. Internal domain adapter classes may change without a major bump as long as the façade shape and `@webex/component-adapter-interfaces` compliance are preserved. + +Evidence: `src/index.js`, `ai-docs/CONTRACTS.md`. + +## Host Integration & Theming + +Hosts typically construct `WebexJSONAdapter` with a datasource object (often from `src/data/*.js` modules in demos) and pass the instance to `withAdapter` or `WebexDataProvider`. Production hosts should use `@webex/sdk-component-adapter` instead of JSON adapters. + +Evidence: `README.md`, `.storybook/`, `src/data/index.js`. + +## Test-Case Strategy (module) + +- Domain tests: `MeetingsJSONAdapter.test.js`, `PeopleJSONAdapter.test.js`, etc. +- Assert observable emissions and control side effects. +- Mock datasource objects inline in tests. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| ADP-R-005 control registry | `MeetingsJSONAdapter.test.js` | none for unregistered `DISABLED_JOIN_CONTROL` | +| ADP-R-003 control constants | `MeetingsJSONAdapter.test.js` | none | + +Evidence: `src/adapters/*.test.js`. + +## Traceability + +| Requirement | Code | Tests | +|---|---|---| +| ADP-R-001 | `src/adapters/WebexJSONAdapter.js` | adapter tests | +| ADP-R-002 | `src/adapters/WebexJSONAdapter.js` | — | +| ADP-R-003 | `src/adapters/MeetingsJSONAdapter.js` | `MeetingsJSONAdapter.test.js` | +| ADP-R-004 | `src/adapters/MeetingsJSONAdapter.js` | MeetingsJSONAdapter tests | +| ADP-R-005 | `src/adapters/MeetingsJSONAdapter/controls/` | `MeetingsJSONAdapter.test.js` | +| ADP-R-006 | `src/adapters/WebexJSONAdapter.js` | adapter tests | +| ADP-R-007 | `src/adapters/PeopleJSONAdapter.js` | PeopleJSONAdapter.test.js | + +- Repo architecture: [`ai-docs/ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md) · Registry: [`ai-docs/SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/src/components/ai-docs/components-spec.md b/src/components/ai-docs/components-spec.md new file mode 100644 index 000000000..17af3f636 --- /dev/null +++ b/src/components/ai-docs/components-spec.md @@ -0,0 +1,376 @@ + + +# components — SPEC + +> Start here → root [`AGENTS.md`](../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | components | +| Source path(s) | `src/components/`, `src/constants.js`, `src/util.js` | +| Doc kind | Module spec | +| Coverage score | 98% assessed 2026-07-27 — all 31 barrel exports documented in Public Surface; hooks, HOCs, contexts, and internal support folders documented | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | cursor-agent-session / pending PR approval / 2026-07-23 | +| Validation status | pass-with-warnings, validator codex-agent-session, assessed 2026-07-23 (0 blocking) | + +## Evidence Rules + +Every requirement cites `file path` evidence. Test evidence preferred for WHY. Unresolved gaps marked `[NEEDS HUMAN INPUT]` only when code/tests do not support a claim. + +## Source Material Register + +| Source material | Scope | Decision | Detail location or disposition | +|---|---|---|---| +| Per-component README.md files | component usage | reference-only | Linked from Overview; not canonical | +| Root README.md | package usage | reference-only | GETTING_STARTED / ARCHITECTURE | +| Storybook stories | UI behavior | verified | Use Cases, UI Flow sections | + +## Overview + +The **components** module is the primary UI surface of `@webex/components`. It contains exported Webex experience components (meetings, messaging, roster, settings, authentication), shared generic widgets (`Button`, `Modal`, …), React hooks that read adapter observables, and HOCs (`withAdapter`, `withMeeting`) that wire adapter lifecycle into the tree. + +Internal folders (`icons/`, `inputs/`, `adaptive-cards/`, `generic/`) support exported components but are not all re-exported from `src/components/index.js`. + +## Purpose / Responsibility + +Owns all React presentation and client-side interaction for Webex component experiences. Does **not** fetch Webex cloud data directly — consumes injected adapters only. + +## Stack + +- JavaScript (JSX), React 18, PropTypes +- RxJS consumption via hooks (peer dependency) +- Jest + React Testing Library for tests +- SCSS co-located per component; aggregated via `src/styles/index.scss` + +## Folder / Package Structure + +``` +src/components/ +├── WebexMeeting/ # Full meeting shell +├── WebexMessaging/ # Messaging UI +├── WebexDataProvider/ # AdapterContext provider +├── hoc/ # withAdapter, withMeeting +├── hooks/ # useMeeting, usePerson, contexts +├── generic/ # Button, Modal, Title, … +├── icons/ # SVG/icon components +├── inputs/ # Form inputs +├── adaptive-cards/ # Adaptive card rendering +├── index.js # Public export barrel +├── helpers.js # className helpers +└── breakpoints.js # Responsive breakpoints +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `src/components/index.js` | Public component and hook exports | +| `src/components/hoc/withAdapter.jsx` | Adapter connect/disconnect HOC | +| `src/components/WebexDataProvider/WebexDataProvider.jsx` | AdapterContext provider | +| `src/components/hooks/contexts.js` | AdapterContext, MeetingContext | +| `src/constants.js` | `WEBEX_COMPONENTS_CLASS_PREFIX`, ARIA label constants | +| `src/util.js` | Shared helpers used by components/adapters | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility | Schema / detail link | Root index | +|---|---|---|---|---|---|---| +| components.WebexAvatar | SDK export | `WebexAvatar` component | Avatar display | Semver beta | `src/components/WebexAvatar/WebexAvatar.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexActivity | SDK export | `WebexActivity` component | Single activity | Semver beta | `src/components/WebexActivity/WebexActivity.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexActivityStream | SDK export | `WebexActivityStream` component | Activity stream | Semver beta | `src/components/WebexActivityStream/WebexActivityStream.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexAdaptiveCards | SDK export | `WebexAdaptiveCards` component | Adaptive cards container | Semver beta | `src/components/WebexAdaptiveCards/WebexAdaptiveCards.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexDataProvider | SDK export | `WebexDataProvider` component | Adapter context provider | Semver beta | `src/components/WebexDataProvider/WebexDataProvider.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexInMeeting | SDK export | `WebexInMeeting` component | In-meeting layout | Semver beta | `src/components/WebexInMeeting/WebexInMeeting.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexInterstitialMeeting | SDK export | `WebexInterstitialMeeting` component | Pre-join interstitial | Semver beta | `src/components/WebexInterstitialMeeting/WebexInterstitialMeeting.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexLocalMedia | SDK export | `WebexLocalMedia` component | Local media display | Semver beta | `src/components/WebexLocalMedia/WebexLocalMedia.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMediaAccess | SDK export | `WebexMediaAccess` component | Media permission prompt | Semver beta | `src/components/WebexMediaAccess/WebexMediaAccess.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeeting | SDK export | `WebexMeeting` component | Full meeting shell | Semver beta | `src/components/WebexMeeting/WebexMeeting.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeetingGuestAuthentication | SDK export | `WebexMeetingGuestAuthentication` component | Guest auth UI | Semver beta | `src/components/WebexMeetingGuestAuthentication/WebexMeetingGuestAuthentication.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeetingHostAuthentication | SDK export | `WebexMeetingHostAuthentication` component | Host auth UI | Semver beta | `src/components/WebexMeetingHostAuthentication/WebexMeetingHostAuthentication.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeetingControl | SDK export | `WebexMeetingControl` component | Single meeting control | Semver beta | `src/components/WebexMeetingControl/WebexMeetingControl.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeetingControlBar | SDK export | `WebexMeetingControlBar` component | Control bar | Semver beta | `src/components/WebexMeetingControlBar/WebexMeetingControlBar.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMeetingInfo | SDK export | `WebexMeetingInfo` component | Meeting info display | Semver beta | `src/components/WebexMeetingInfo/WebexMeetingInfo.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMember | SDK export | `WebexMember` component | Single member row | Semver beta | `src/components/WebexMember/WebexMember.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMemberRoster | SDK export | `WebexMemberRoster` component | Member roster | Semver beta | `src/components/WebexMemberRoster/WebexMemberRoster.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexMessaging | SDK export | `WebexMessaging` component | Messaging UI | Semver beta | `src/components/WebexMessaging/WebexMessaging.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexRemoteMedia | SDK export | `WebexRemoteMedia` component | Remote media tiles | Semver beta | `src/components/WebexRemoteMedia/WebexRemoteMedia.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexSettings | SDK export | `WebexSettings` component | Settings panel | Semver beta | `src/components/WebexSettings/WebexSettings.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexWaitingForHost | SDK export | `WebexWaitingForHost` component | Waiting state UI | Semver beta | `src/components/WebexWaitingForHost/WebexWaitingForHost.jsx` | `ai-docs/CONTRACTS.md` | +| components.SignIn | SDK export | `SignIn` component | Sign-in UI | Semver beta | `src/components/SignIn/SignIn.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexSearchPeople | SDK export | `WebexSearchPeople` component | People search UI | Semver beta | `src/components/WebexSearchPeople/WebexSearchPeople.jsx` | `ai-docs/CONTRACTS.md` | +| components.WebexCreateSpace | SDK export | `WebexCreateSpace` component | Create space UI | Semver beta | `src/components/WebexCreateSpace/WebexCreateSpace.jsx` | `ai-docs/CONTRACTS.md` | +| components.useMeetingDestination | SDK export | `useMeetingDestination` hook | Resolve meeting destination | Semver beta | `src/components/hooks/useMeetingDestination.js` | `ai-docs/CONTRACTS.md` | +| components.withMeeting | SDK export | `withMeeting` HOC | Meeting scope wrapper | Semver beta | `src/components/hoc/withMeeting.jsx` | `ai-docs/CONTRACTS.md` | +| components.withAdapter | SDK export | `withAdapter` HOC | Adapter lifecycle injection | Semver beta | `src/components/hoc/withAdapter.jsx` | `ai-docs/CONTRACTS.md` | +| components.Button | SDK export | `Button` component | Generic button | Semver beta | `src/components/generic/Button/Button.jsx` | `ai-docs/CONTRACTS.md` | +| components.Modal | SDK export | `Modal` component | Generic modal | Semver beta | `src/components/generic/Modal/Modal.jsx` | `ai-docs/CONTRACTS.md` | +| components.AdapterContext | SDK export | `AdapterContext` context | React context for adapter | Semver beta | `src/components/hooks/contexts.js` | `ai-docs/CONTRACTS.md` | +| components.MeetingContext | SDK export | `MeetingContext` context | React context for meeting scope | Semver beta | `src/components/hooks/contexts.js` | `ai-docs/CONTRACTS.md` | + +Evidence: `src/components/index.js` (31 barrel exports). + +## Requires (dependencies) + +- `@webex/component-adapter-interfaces` adapter instances on `AdapterContext` (activities, meetings, memberships, people, rooms adapters minimum per `WebexDataProvider.propTypes`) +- Peer: `react`, `react-dom`, `prop-types`, `rxjs` +- Internal: `src/adapters/` for Storybook/demo; production hosts use SDK adapters +- Styles from `src/styles/` imported at package entry + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence | +|---|---|---|---|---|---|---| +| COMP-R-001 | Exported components MUST be listed in `src/components/index.js` | Defines npm public API | `src/components/index.js` | `package.json` exports via Rollup entry | none | PRESENT | +| COMP-R-002 | `withAdapter` MUST NOT wrap with `WebexDataProvider` until `adapter.connect()` resolves; wrapped component may render without context meanwhile | Prevents hooks from reading adapter before connect; allows loading UI via `adapterConnected` prop | `src/components/hoc/withAdapter.jsx` | `src/components/hoc/withAdapter.test.jsx` | none | PRESENT | +| COMP-R-003 | `WebexDataProvider` MUST require adapter shape with five domain adapters | Matches adapter interface contract | `src/components/WebexDataProvider/WebexDataProvider.jsx` | `src/components/WebexDataProvider/WebexDataProvider.test.js` | organizations adapter optional in propTypes | PRESENT | +| COMP-R-004 | DOM class prefix MUST use `wxc` constant | Consistent theming/CSS | `src/constants.js` | `src/styles/_variables.scss` | none | PRESENT | +| COMP-R-005 | Exported component folders SHOULD follow co-located test and Storybook pattern | Regression safety for public API | `CONTRIBUTING.md` | `src/components/**/*.test.js`, `*.stories.js` | not every export has stories | PRESENT | +| COMP-R-006 | All exported symbols MUST match `src/components/index.js` barrel | npm public API contract | `src/components/index.js` | build/export tests | none | PRESENT | +| COMP-R-007 | Internal hooks under `hooks/` MUST subscribe/unsubscribe adapter observables safely | Prevents memory leaks on unmount | `src/components/hooks/useMeeting.js` | hook tests | none | PRESENT | +| COMP-R-008 | `WebexDataProvider` PropTypes require five domain adapters (activities, meetings, memberships, people, rooms) | Documents minimum adapter shape for context | `src/components/WebexDataProvider/WebexDataProvider.jsx` | WebexDataProvider.test.js | organizationsAdapter on WebexJSONAdapter but not in PropTypes — host/SDK may still supply | PRESENT | + +## Design Overview + +Components follow a **container/presentation** split where data arrives exclusively through adapter hooks. `withAdapter` owns adapter instantiation and async connect/disconnect; presentational components remain testable with mock adapters in JSON form. + +Hooks encapsulate RxJS subscription lifecycle for meeting, person, room, and activity streams. Generic components (`Button`, `Modal`) provide shared UX primitives with Webex styling. + +Supporting folders (`icons/`, `inputs/`, `generic/`, `adaptive-cards/`) implement internal UI pieces. Only `Button` and `Modal` from `generic/` are npm-exported; other support components are consumed internally (see Class / Component Relationships). + +## Data Flow + +```mermaid +flowchart TB + HostProps[Host props / access token] + Factory[adapterFactory in withAdapter] + Adapter[WebexAdapter instance] + Connect[adapter.connect] + Provider[WebexDataProvider] + Ctx[AdapterContext] + Hook[useMeeting / usePerson / …] + UI[Webex* component render] + + HostProps --> Factory + Factory --> Adapter + Adapter --> Connect + Connect --> Provider + Provider --> Ctx + Ctx --> Hook + Hook --> UI + UI -->|control action| Adapter +``` + +Data flows one way from adapter observables into hooks, then into React render output. User actions invoke adapter control objects (defined in adapter module). + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Adapter connect + meeting subscribe | below | first paint without provider; provider added after connect | + +```mermaid +sequenceDiagram + participant Host + participant WithAdapter + participant Adapter + participant Provider as WebexDataProvider + participant Comp as WebexMeeting + + Host->>WithAdapter: render with props + WithAdapter->>Adapter: adapterFactory(props) + WithAdapter->>Comp: render without provider (adapterConnected=false) + WithAdapter->>Adapter: connect() + Adapter-->>WithAdapter: connected + WithAdapter->>Provider: adapter + Provider->>Comp: AdapterContext available (adapterConnected=true) + Comp->>Adapter: subscribe meeting observables + Adapter-->>Comp: meeting state updates +``` + +## Class / Component Relationships + +```mermaid +classDiagram + class withAdapter { + +WrappedComponent + +adapterFactory + } + class WebexDataProvider { + +adapter + +children + } + class WebexMeeting { + +meetingID + +layout + +controls + } + class useMeeting { + <> + } + withAdapter --> WebexDataProvider : wraps when connected + WebexDataProvider --> WebexMeeting : provides context + WebexMeeting --> useMeeting : reads state +``` + +Relationship: HOC → Provider → feature components → hooks → adapter interfaces (external package). + +**Internal components (not in public barrel):** + +| Component folder | Role | Used by | +|---|---|---| +| `WebexAdaptiveCard/` | Renders single adaptive card | `WebexAdaptiveCards` | +| `WebexAudioSettings/` | Audio device selection UI | `WebexSettings` | +| `WebexVideoSettings/` | Video device selection UI | `WebexSettings` | +| `WebexMeetingProvider/` | Provides meeting context | Meeting subtree | +| `WebexNoMedia/` | Placeholder when no media | Media components | + +**Hooks catalog** (`src/components/hooks/` — only `useMeetingDestination` exported from package barrel): + +| Hook | Purpose | Evidence | +|---|---|---| +| `useMeeting` | Subscribe to current meeting observables | `src/components/hooks/useMeeting.js` | +| `useMeetingControl` | Access meeting control instances | `src/components/hooks/useMeetingControl.js` | +| `useMeetingDestination` | Resolve meeting destination (exported) | `src/components/hooks/useMeetingDestination.js` | +| `usePerson` | Person data by ID | `src/components/hooks/usePerson.js` | +| `useMe` | Current user person data | `src/components/hooks/useMe.js` | +| `useRoom` | Room/space data | `src/components/hooks/useRoom.js` | +| `useMembers` | Membership roster | `src/components/hooks/useMembers.js` | +| `useActivity` | Single activity | `src/components/hooks/useActivity.js` | +| `useActivityStream` | Activity stream | `src/components/hooks/useActivityStream.js` | + +Additional hooks: `useActivityScroll`, `useOverflowActivities`, `useAdaptiveCard`, `useOrganization`, `useStream`, `useSpeakers`, `useMetrics`, `useElementDimensions`, `useElementPosition`, `useAutoFocus`, `useRef` — see `src/components/hooks/index.js`. + +Evidence: `src/components/` directory listing, import graph from `WebexMeeting.jsx`. + +## Use Cases + +| UC | Actor | Flow | Evidence | +|---|---|---|---| +| UC-1 Embed meeting | Host developer | Wrap route with `withAdapter(WebexMeeting, factory)` | `README.md`, stories | +| UC-2 Demo offline | Storybook author | Pass `WebexJSONAdapter` datasource | `.storybook/`, adapters module | +| UC-3 Custom controls | Host developer | Pass `controls` render prop to `WebexMeeting` | `src/components/WebexMeeting/WebexMeeting.jsx` | + +## UI Flow + +Primary meeting UI states driven by `MeetingState` from adapter interfaces: + +1. Authentication / waiting (guest/host auth components) +2. Interstitial (pre-join) +3. In-meeting (media, controls, roster) +4. Settings modal overlay + +Evidence: imports in `src/components/WebexMeeting/WebexMeeting.jsx`. + +## State Model + +| State layer | Owner | Transitions | +|---|---|---| +| Adapter observables | adapters module | Meeting join/leave, mute, roster — via control objects | +| React local state | components | Modal open, layout dimensions, UI-only toggles | +| AdapterContext | components | Set when `WebexDataProvider` mounts after connect | +| MeetingContext | components | Meeting-scoped subtree via `withMeeting` | + +No Redux/global store — all domain state flows from adapter subscriptions. + +Evidence: `src/components/hooks/contexts.js`, `src/components/hoc/withMeeting.jsx`, `src/adapters/MeetingsJSONAdapter.js`. + +## Concurrency & Reactive Flow + +Component hooks subscribe to RxJS observables from adapter methods. Subscriptions must clean up on unmount (handled in hook implementations). Multiple components may subscribe to the same meeting observable; adapter emits push updates. + +Evidence: `src/components/hooks/useMeeting.js`, `src/adapters/MeetingsJSONAdapter.js`, `rxjs` peer dependency. + +## State Machine + +Meeting shell UI transitions follow adapter-reported `MeetingState` (from `@webex/component-adapter-interfaces`). `WebexMeeting` selects child components based on meeting status and auth requirements. + +```mermaid +stateDiagram-v2 + [*] --> AuthOrWaiting: render WebexMeeting + AuthOrWaiting --> Interstitial: authenticated / guest flow complete + Interstitial --> InMeeting: join success + InMeeting --> Interstitial: leave / end + InMeeting --> SettingsOverlay: open settings + SettingsOverlay --> InMeeting: close settings +``` + +Evidence: `src/components/WebexMeeting/WebexMeeting.jsx`, `@webex/component-adapter-interfaces` `MeetingState`. + +## Pitfalls + +- Importing components without CSS yields unstyled UI — load `dist/css/webex-components.css`. +- Assuming `organizationsAdapter` on provider when only five adapters validated in propTypes — verify host adapter shape. +- Snapshot tests sensitive to class names — coordinate with styles module when changing prefix. +- `withAdapter` renders the wrapped component immediately without `WebexDataProvider` while connect is pending — use `adapterConnected` prop for loading UI; do not assume `AdapterContext` on first paint. + +Evidence: `src/components/hoc/withAdapter.jsx`, `src/util.js`. + +## Module Do's / Don'ts + +- DO: one folder per component with `ComponentName.jsx` + co-located `.scss`, `.test.js`, `.stories.js`. +- DO: use `webexComponentClasses()` from `src/components/helpers.js` for BEM-style class names. +- DO: use responsive breakpoints from `src/components/breakpoints.js` (e.g. `PHONE_LARGE`). +- DON'T: fetch Webex cloud data directly from components — use adapter hooks only. +- DON'T: export internal support components without updating `src/components/index.js` and `CONTRACTS.md`. + +Evidence: `CONTRIBUTING.md`, `src/components/helpers.js`. + +## Export Stability + +Public exports are semver-managed via semantic-release. Beta status documented in README — consumers should pin versions. Adding optional props is minor; removing exports or required props is major. + +Evidence: `README.md` Project Status, `package.json` release scripts. + +## Host Integration & Theming + +Hosts MUST: + +1. Install peer dependencies (`react`, `react-dom`, `rxjs`, `prop-types`). +2. Import compiled CSS from package `dist/css/webex-components.css`. +3. Provide adapter via `withAdapter` or manual `WebexDataProvider`. +4. For production Webex data, use SDK adapter from `@webex/sdk-component-adapter` (external repo) — not bundled here. + +Evidence: `README.md`, `package.json` peerDependencies. + +## Test-Case Strategy (module) + +- Co-located Jest tests per component (`*.test.js`, `*.test.jsx`). +- Snapshot tests for stable DOM structure where established. +- Hook tests with mock adapter context. +- Storybook for visual states; Chromatic in CI. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| COMP-R-002 connect-before-provider | `src/components/hoc/withAdapter.test.jsx` | none | +| COMP-R-005 co-located tests | `src/components/**/*.test.js` | not all exports have stories | +| COMP-R-007 hook cleanup | hook tests under `src/components/hooks/` | none | + +Evidence: `src/components/**/*.test.js`, `.circleci/config.yml`. + +## Traceability + +| Requirement | Code | Tests | +|---|---|---| +| COMP-R-001 | `src/components/index.js` | Rollup build smoke | +| COMP-R-002 | `src/components/hoc/withAdapter.jsx` | `src/components/hoc/withAdapter.test.jsx` | +| COMP-R-003 | `src/components/WebexDataProvider/WebexDataProvider.jsx` | `src/components/WebexDataProvider/WebexDataProvider.test.js` | +| COMP-R-004 | `src/constants.js` | style/component tests | +| COMP-R-005 | `src/components/` | `src/components/**/*.test.js` | +| COMP-R-006 | `src/components/index.js` | build | +| COMP-R-007 | `src/components/hooks/useMeeting.js` | hook tests | +| COMP-R-008 | `src/components/WebexDataProvider/WebexDataProvider.jsx` | WebexDataProvider.test.js | + +- Repo architecture: [`ai-docs/ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md) · Registry: [`ai-docs/SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) +- Coverage state & contracts baseline: `.sdd/manifest.json` diff --git a/src/styles/ai-docs/styles-themes-spec.md b/src/styles/ai-docs/styles-themes-spec.md new file mode 100644 index 000000000..884f71298 --- /dev/null +++ b/src/styles/ai-docs/styles-themes-spec.md @@ -0,0 +1,253 @@ + + +# styles-themes — SPEC + +> Start here → root [`AGENTS.md`](../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md). + +## Metadata + +| Field | Value | +|---|---| +| Module id | styles-themes | +| Source path(s) | `src/styles/`, `src/themes/`, `src/assets/` | +| Doc kind | Module spec | +| Coverage score | 95% assessed 2026-07-23 — SCSS graph, themes, fonts, and build outputs documented | +| Generated from | `module-spec` @ SDLC template library `0.2.1` | +| generated_by / approved_by / updated_at | cursor-agent-session / pending PR approval / 2026-07-23 | +| Validation status | pass-with-warnings, validator codex-agent-session, assessed 2026-07-23 (0 blocking) | + +## Evidence Rules + +Style requirements cite SCSS entry files and Rollup copy targets. Class prefix cross-checks `src/constants.js`. + +## Source Material Register + +| Source material | Scope | Decision | Detail location | +|---|---|---|---| +| Component `_components.scss` | per-component styles | verified | Imported via `src/styles/index.scss` | +| Storybook | visual verification | reference-only | `.storybook/` | + +## Overview + +The **styles-themes** module defines global SCSS (variables, mixins, colors, fonts, defaults), dark/light theme token files, and font assets copied into `dist/` during build. Package entry `src/index.js` side-imports `src/styles/index.scss` so bundlers emit consolidated CSS. + +## Purpose / Responsibility + +Own visual consistency for Webex Components (`wxc-` class prefix) and deliver compiled CSS + theme assets to npm consumers. Does **not** implement component logic. + +## Stack + +- SCSS (sass) +- Rollup `rollup-plugin-scss` → `dist/css/webex-components.css` +- Rollup `rollup-plugin-copy` for themes and fonts + +## Folder / Package Structure + +``` +src/styles/ +├── index.scss # Entry: imports mixins, variables, themes, components +├── _variables.scss # SCSS variables (incl. class prefix) +├── _colors.scss +├── _mixins.scss +├── _fonts.scss +└── _defaults.scss + +src/themes/ +├── dark.scss +├── light.scss +├── dark/webex-logo.svg +└── light/webex-logo.svg + +src/assets/fonts/ # Copied to dist/assets on build +``` + +## Key Files (source of truth) + +| File | Holds | +|---|---| +| `src/styles/index.scss` | SCSS import graph root | +| `src/styles/_variables.scss` | Design tokens; class prefix must match `src/constants.js` | +| `src/themes/dark.scss` | Dark theme variables | +| `src/themes/light.scss` | Light theme variables | +| `rollup.config.js` | SCSS output path and copy targets | +| `src/constants.js` | `WEBEX_COMPONENTS_CLASS_PREFIX = 'wxc'` | + +## Public Surface + +| Contract ID | Type | Surface | Purpose | Compatibility | Detail link | +|---|---|---|---|---|---| +| styles.css.bundle | npm asset | `dist/css/webex-components.css` | Compiled styles for hosts | Additive class changes preferred | `rollup.config.js` | +| styles.themes.dark | npm asset | `dist/themes/dark/` | Dark theme static assets | Semver | `rollup.config.js` | +| styles.themes.light | npm asset | `dist/themes/light/` | Light theme assets | Semver | `rollup.config.js` | +| styles.fonts | npm asset | `dist/assets/fonts/` | Font files | Semver | `rollup.config.js` | + +Internal Surface — SCSS partials are build-time, not direct public API. + +## Requires (dependencies) + +- `node-sass` / sass toolchain (devDependency via build) +- Component SCSS partials under `src/components/_components.scss` and per-component styles + +## Requirements + +| ID | WHAT | WHY | Source Evidence | Test Evidence | Gaps | Confidence | +|---|---|---|---|---|---|---| +| STY-R-001 | Package entry MUST import global SCSS | Ensures CSS emitted in library build | `src/index.js` | build output | none | PRESENT | +| STY-R-002 | Class prefix in SCSS MUST match JS constant `wxc` | Prevents broken selectors | `src/styles/_variables.scss`, `src/constants.js` | component tests | none | PRESENT | +| STY-R-003 | Rollup MUST emit compressed CSS to `dist/css/webex-components.css` | Consumer import path | `rollup.config.js` | CI build | none | PRESENT | +| STY-R-004 | Build MUST copy theme folders to `dist/themes/` | Host theme switching | `rollup.config.js` copy plugin | build artifact | none | PRESENT | +| STY-R-005 | Build MUST copy font files to `dist/assets/fonts/` | Hosts load bundled fonts | `rollup.config.js` copy plugin | CI build | none | PRESENT | +| STY-R-006 | Theme SCSS files MUST be imported from `src/styles/index.scss` | Single CSS bundle | `src/styles/index.scss` | build | none | PRESENT | +| STY-R-007 | Component styles aggregated via `src/components/_components.scss` | Component-level SCSS inclusion | `src/components/_components.scss` | build | none | PRESENT | + +## Design Overview + +SCSS is organized in layers: variables/mixins → color/font tokens → theme overrides → component aggregation. Themes (`dark.scss`, `light.scss`) set CSS custom properties or SCSS variables consumed by component rules. Rollup compiles a single CSS bundle for simplicity; theme folders ship as static assets for logos and theme-specific resources. + +## Data Flow + +```mermaid +flowchart LR + Index[src/index.js import scss] + Styles[src/styles/index.scss] + Components[src/components/_components.scss] + Rollup[rollup-plugin-scss] + CSS[dist/css/webex-components.css] + Copy[rollup-plugin-copy] + DistThemes[dist/themes] + DistFonts[dist/assets/fonts] + + Index --> Styles + Styles --> Components + Styles --> Rollup + Rollup --> CSS + Copy --> DistThemes + Copy --> DistFonts +``` + +## Sequence Diagram(s) + +Sequence coverage: + +| Operation group | Diagram | Failure / recovery coverage | +|---|---|---| +| Production build CSS + assets | below | SCSS `failOnError: true` aborts build on compile failure | + +```mermaid +sequenceDiagram + participant Dev as Developer + participant Rollup + participant SCSS as rollup-plugin-scss + participant Copy as rollup-plugin-copy + + Dev->>Rollup: npm run build + Rollup->>SCSS: compile src/index.js graph + SCSS-->>Rollup: dist/css/webex-components.css + Rollup->>Copy: copy themes + fonts + Copy-->>Rollup: dist/themes, dist/assets +``` + +## Class / Component Relationships + +- SCSS `_variables.scss` defines `$wxc-*` tokens consumed by component SCSS files. +- `helpers.js` `webexComponentClasses()` generates BEM-style class strings aligned with SCSS blocks. +- No OOP classes in this module — relationship is **token → component stylesheet → rendered DOM class**. + +Evidence: `src/components/helpers.js`, `src/styles/_variables.scss`. + +## Use Cases + +| UC | Description | Evidence | +|---|---|---| +| UC-1 Consumer styling | Host imports `dist/css/webex-components.css` | `README.md` | +| UC-2 Theme assets | Host serves `dist/themes/dark` logos | `rollup.config.js` | +| UC-3 Component author | Add SCSS beside new component; register in `_components.scss` | `src/components/` patterns | + +## SCSS File Registry + +| File | Role | +|---|---| +| `src/styles/index.scss` | Entry import graph | +| `src/styles/_variables.scss` | Prefix, fonts, meeting min dimensions | +| `src/styles/_colors.scss` | Color tokens | +| `src/styles/_mixins.scss` | Shared mixins | +| `src/styles/_fonts.scss` | Font face declarations | +| `src/styles/_defaults.scss` | Base element defaults | +| `src/themes/dark.scss` | Dark theme tokens | +| `src/themes/light.scss` | Light theme tokens | +| `src/components/_components.scss` | Imports all component SCSS partials | + +## Theme & Asset Assets + +| Path | Role | +|---|---| +| `src/themes/dark/webex-logo.svg` | Dark theme logo | +| `src/themes/light/webex-logo.svg` | Light theme logo | +| `src/assets/fonts/` | CiscoSansTT font files copied to `dist/assets/fonts/` | + +Evidence: `rollup.config.js`, `src/assets/fonts/`. + +## Pitfalls + +- Changing `$prefix` in SCSS without updating `WEBEX_COMPONENTS_CLASS_PREFIX` breaks tests and styles. +- SCSS compilation failures fail build (`failOnError: true` in Rollup scss plugin). +- Consumers who tree-shake JS but omit CSS see unstyled components. + +Evidence: `rollup.config.js`, `src/constants.js`. + +## Module Do's / Don'ts + +- DO: prefix SCSS partials with `_` under `src/styles/`. +- DO: keep `$WEBEX_COMPONENTS_CLASS_PREFIX` aligned with `src/constants.js`. +- DO: register new component SCSS in `src/components/_components.scss`. +- DON'T: change `wxc-` class prefix in only JS or only SCSS. +- DON'T: expect per-component CSS tree-shaking from the single bundle model. + +Evidence: `src/styles/_variables.scss`, `src/constants.js`. + +## Export Stability + +Published npm assets (`dist/css/`, `dist/themes/`, `dist/assets/`) follow semver via semantic-release. Prefer additive CSS classes; renaming `wxc-` prefixed classes is breaking. + +Evidence: `package.json`, `README.md`. + +## Host Integration & Theming + +Hosts load compiled CSS once globally. Theme selection may swap CSS variables or load theme-specific assets from `dist/themes/`. Class prefix `wxc` must not be overridden without updating both JS and SCSS. + +Evidence: `README.md`, `dist/css/webex-components.css`. + +## Test-Case Strategy (module) + +- Indirect: component snapshot tests assert expected `wxc-` classes. +- Build verification: `npm run build` produces expected dist artifacts. +- Visual: Storybook/Chromatic for theme regressions. + +| Behavior / Requirement | Existing test evidence | Gap | +|---|---|---| +| STY-R-003 CSS bundle | CI build | none | +| STY-R-005 font copy | CI build / dist inspection | none | + +Evidence: `.circleci/config.yml`, `rollup.config.js`. + +## Traceability + +| Requirement | Code | Tests | +|---|---|---| +| STY-R-001 | `src/index.js` | build | +| STY-R-002 | `src/styles/_variables.scss`, `src/constants.js` | component snapshots | +| STY-R-003 | `rollup.config.js` | CI build | +| STY-R-004 | `rollup.config.js` | CI build | +| STY-R-005 | `rollup.config.js` | CI build | +| STY-R-006 | `src/styles/index.scss` | build | +| STY-R-007 | `src/components/_components.scss` | build | + +- Repo architecture: [`ai-docs/ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md) · Registry: [`ai-docs/SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) +- Coverage state & contracts baseline: `.sdd/manifest.json`