@agentman/chat-widget
Version:
Agentman Chat Widget for easy integration with web applications
777 lines (620 loc) • 17.7 kB
Markdown
# Stage 2: Incremental Config Consolidation - v3.0 Clean Break
## Philosophy
**Clean v3.0 with incremental development:**
1. NO backward compatibility - Clean code, no deprecated properties
2. Migrate one namespace at a time - Smaller changes, easier testing
3. Develop on separate branch - v2.x stays stable on main
4. Deploy to separate repo/CDN when complete - Users choose v2.x or v3.0
This approach:
- ✅ **Not disruptive:** Incremental phases = smaller changesets, easier debugging
- ✅ **No dead code:** Zero backward compatibility logic
- ✅ **Parallel development:** v2.x stable on main, v3.0 on branch
- ✅ **Safe deployment:** Test everything before exposing to users
## Strategy: Clean Break + Incremental Implementation
### Core Approach
```typescript
// v3.0 - Clean structure, no old properties
interface ChatConfig {
api: ApiConfig; // Required, no fallback
variant: ChatVariant;
containerId: string;
// ... new namespaced structure only
}
// ConfigManager - Clean accessors
class ConfigManager {
getApiUrl(): string {
return this.config.api.url; // No fallback, no deprecation warnings
}
}
```
### Development Workflow
1. **Create `v3-clean-config` branch** - All v3 work happens here
2. **Implement 10 phases incrementally** - Commit after each phase
3. **Test thoroughly** - Smaller changes = easier to verify
4. **Deploy when complete** - To new repo/CDN as v3.0.0
## 10 Incremental Phases
Each phase is small, focused, and independently testable.
### Phase 1: API Config ⏱️ 30 minutes
**Goal:** Replace flat `apiUrl`/`agentToken` with `api` namespace
#### Changes
**Type Definitions:**
```typescript
// assistantWidget/types/types.ts
export interface ApiConfig {
url: string;
token: string;
}
export interface ChatConfig {
api: ApiConfig; // NEW: Required namespace
// REMOVED: apiUrl, agentToken
variant: ChatVariant;
containerId: string;
// ... rest
}
```
**ConfigManager:**
```typescript
// assistantWidget/config/ConfigManager.ts
getApiUrl(): string {
return this.config.api.url; // Clean, no fallback
}
getApiToken(): string {
return this.config.api.token; // Clean, no fallback
}
private validateApiConfig(): void {
if (!this.config.api?.url || !this.config.api?.token) {
throw new Error('api.url and api.token are required');
}
}
```
**Test Files:**
```javascript
// Before
const widget = new ChatWidget({
apiUrl: 'http://localhost:8000',
agentToken: 'abc123',
variant: 'corner',
containerId: 'test'
});
// After
const widget = new ChatWidget({
api: {
url: 'http://localhost:8000',
token: 'abc123'
},
variant: 'corner',
containerId: 'test'
});
```
**Deliverable:** Clean API namespace, all tests pass
### Phase 2: Assets Config ⏱️ 30 minutes
**Goal:** Consolidate `logo`, `headerLogo`, `icons` into `assets` namespace
#### Changes
**Type Definitions:**
```typescript
export interface AssetsConfig {
logo: string;
headerLogo: string;
icons?: {
close?: string;
send?: string;
minimize?: string;
maximize?: string;
expand?: string;
collapse?: string;
reduce?: string;
};
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig; // NEW: Required namespace
// REMOVED: logo, headerLogo, icons from root
}
```
**ConfigManager:**
```typescript
getLogo(): string {
return this.config.assets.logo; // Clean, no fallback
}
getHeaderLogo(): string {
return this.config.assets.headerLogo; // Clean, no fallback
}
getIcon(name: string): string | undefined {
return this.config.assets.icons?.[name];
}
```
**Deliverable:** Clean assets namespace
### Phase 3: Layout Config ⏱️ 45 minutes
**Goal:** Group positioning into `layout.desktop` and `layout.mobile`
#### Changes
**Type Definitions:**
```typescript
export interface LayoutConfig {
desktop?: {
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
width?: string;
height?: string;
initiallyOpen?: boolean;
};
mobile?: {
height?: string;
maxHeight?: string;
};
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig; // NEW: Optional namespace
// REMOVED: position, initialWidth, initialHeight, initiallyOpen, mobileHeight, mobileMaxHeight
}
```
**ConfigManager:**
```typescript
getDesktopPosition(): string | undefined {
return this.config.layout?.desktop?.position;
}
getDesktopWidth(): string | undefined {
return this.config.layout?.desktop?.width;
}
getMobileHeight(): string | undefined {
return this.config.layout?.mobile?.height;
}
```
**Update Consumers:**
- `ChatWidget.ts` - Use `config.layout.desktop.*`
- `UIManager.ts` - Use `config.layout.mobile.*`
- `InputBarSheetController.ts` - Use `config.layout.mobile.height`
**Deliverable:** Clean layout namespace, desktop/mobile separated
### Phase 4: Theme Colors ⏱️ 45 minutes
**Goal:** Nest colors into `theme.colors`
#### Changes
**Type Definitions:**
```typescript
export interface ThemeColors {
text: string;
background: string;
button: string;
buttonText: string;
agentMessage: string;
userMessage: string;
}
export interface ChatTheme {
colors: ThemeColors; // NEW: Required sub-namespace
// REMOVED: textColor, backgroundColor, buttonColor, etc.
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig;
theme?: Partial<ChatTheme>; // Optional with defaults
}
```
**ConfigManager:**
```typescript
getTextColor(): string {
return this.config.theme?.colors?.text ?? '#0f172a';
}
getBackgroundColor(): string {
return this.config.theme?.colors?.background ?? '#ffffff';
}
```
**Update Consumers:**
- All files in `assistantWidget/styles/` - Use `theme.colors.*`
- `StyleManager.ts` - Update theme application
**Deliverable:** Clean theme.colors namespace
### Phase 5: Theme Toggle ⏱️ 30 minutes
**Goal:** Consolidate toggle theme, eliminate `toggleStyle` duplication
#### Changes
**Type Definitions:**
```typescript
export interface ToggleTheme {
background: string;
text: string;
icon: string;
content?: string; // Toggle button text
}
export interface ChatTheme {
colors: ThemeColors;
toggle: ToggleTheme; // NEW: Required sub-namespace
// REMOVED: toggleBackgroundColor, toggleTextColor, toggleIconColor
}
export interface ChatConfig {
// ...
theme?: Partial<ChatTheme>;
// REMOVED: toggleStyle (duplicate eliminated!)
// REMOVED: toggleText (moved to theme.toggle.content)
}
```
**ConfigManager:**
```typescript
getToggleBackground(): string {
return this.config.theme?.toggle?.background ?? '#1e1b4b';
}
getToggleText(): string {
return this.config.theme?.toggle?.content ?? 'Chat';
}
```
**Deliverable:** Clean theme.toggle, NO duplication
### Phase 6: Theme InputBar ⏱️ 30 minutes
**Goal:** Consolidate input bar theme properties
#### Changes
**Type Definitions:**
```typescript
export interface InputBarTheme {
brandText?: string;
brandBackground?: string;
brandTextColor?: string;
logoBackground?: string;
logoIcon?: string;
buttonBackground?: string;
buttonIcon?: string;
glowColor?: string;
}
export interface ChatTheme {
colors: ThemeColors;
toggle: ToggleTheme;
inputBar?: InputBarTheme; // NEW: Optional sub-namespace
// REMOVED: inputBarBrandBackground, inputBarBrandText, etc.
}
export interface ChatConfig {
// ...
theme?: Partial<ChatTheme>;
// REMOVED: inputBarBrandText from root
}
```
**ConfigManager:**
```typescript
getInputBarBrandText(): string | undefined {
return this.config.theme?.inputBar?.brandText;
}
```
**Update Consumers:**
- `input-bar-sheet.ts` - Use `theme.inputBar.*`
- `InputBarSheetController.ts` - Use `theme.inputBar.brandText`
**Deliverable:** Complete clean theme consolidation
### Phase 7: Content Config ⏱️ 30 minutes
**Goal:** Group user-facing strings into `content` namespace
#### Changes
**Type Definitions:**
```typescript
export interface ContentConfig {
title?: string;
placeholder?: string;
initialMessage?: string;
welcome?: {
message?: string;
prompts?: [string?, string?, string?];
};
disclaimer?: {
enabled: boolean;
message: string;
linkText?: string;
linkUrl?: string;
};
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig;
theme?: Partial<ChatTheme>;
content?: ContentConfig; // NEW: Optional namespace
// REMOVED: title, placeholder, initialMessage, messagePrompts, disclaimer from root
}
```
**ConfigManager:**
```typescript
getTitle(): string | undefined {
return this.config.content?.title;
}
getPlaceholder(): string | undefined {
return this.config.content?.placeholder;
}
getWelcomeMessage(): string | undefined {
return this.config.content?.welcome?.message;
}
```
**Update Consumers:**
- `ViewManager.ts` - Use `content.title`, `content.placeholder`
- `WelcomeScreen.ts` - Use `content.welcome.*`
**Deliverable:** Clean content namespace
### Phase 8: Features Config ⏱️ 45 minutes
**Goal:** Consolidate feature flags into `features` namespace
#### Changes
**Type Definitions:**
```typescript
export interface FeaturesConfig {
attachments?: {
enabled?: boolean;
maxFileSize?: number;
allowedTypes?: string[];
};
streaming?: {
enabled?: boolean;
preferStreaming?: boolean;
fallbackToNonStreaming?: boolean;
};
persistence?: {
enabled: boolean;
storageKey?: string;
maxConversations?: number;
maxMessagesPerConversation?: number;
ttlDays?: number;
};
metadata?: {
collectClientMetadata?: boolean;
collectIPAddress?: boolean;
custom?: Record<string, any>;
};
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig;
theme?: Partial<ChatTheme>;
content?: ContentConfig;
features?: FeaturesConfig; // NEW: Optional namespace
// REMOVED: enableAttachments, streaming, persistence, collectClientMetadata, etc.
}
```
**ConfigManager:**
```typescript
isAttachmentsEnabled(): boolean {
return this.config.features?.attachments?.enabled ?? true;
}
isStreamingEnabled(): boolean {
return this.config.features?.streaming?.enabled ?? true;
}
isPersistenceEnabled(): boolean {
return this.config.features?.persistence?.enabled ?? false;
}
```
**Update Consumers:**
- `FileHandler.ts` - Use `features.attachments.*`
- `MessageHandler.ts` - Use `features.streaming.*`
- `PersistenceManager.ts` - Use `features.persistence.*`
**Deliverable:** Clean features namespace
### Phase 9: Closed View Config ⏱️ 20 minutes
**Goal:** Create `closedView` namespace
#### Changes
**Type Definitions:**
```typescript
export interface ClosedViewConfig {
mode: ClosedViewMode;
delay?: number; // Delay before showing (ms)
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig;
theme?: Partial<ChatTheme>;
content?: ContentConfig;
features?: FeaturesConfig;
closedView?: ClosedViewConfig; // NEW: Optional namespace
// REMOVED: agentClosedView from root
}
```
**ConfigManager:**
```typescript
getClosedViewMode(): ClosedViewMode | undefined {
return this.config.closedView?.mode;
}
getClosedViewDelay(): number {
return this.config.closedView?.delay ?? 0;
}
```
**Update Consumers:**
- `ChatWidget.ts` - Use `config.closedView.mode`
- `ViewManager.ts` - Use `config.closedView.delay`
**Deliverable:** Clean closedView namespace
### Phase 10: Advanced Config ⏱️ 30 minutes
**Goal:** Group advanced settings into `advanced` namespace
#### Changes
**Type Definitions:**
```typescript
export interface AdvancedConfig {
debug?: {
enabled: boolean;
logLevel?: 'error' | 'warn' | 'info' | 'debug' | 'verbose';
timestamps?: boolean;
console?: boolean;
logger?: (level: string, message: string, data?: any) => void;
};
markdown?: {
cdnUrls?: string[];
timeout?: number;
options?: {
gfm?: boolean;
breaks?: boolean;
headerIds?: boolean;
mangle?: boolean;
pedantic?: boolean;
smartLists?: boolean;
smartypants?: boolean;
};
};
animations?: {
forceAnimations?: boolean;
customDurations?: {
expand?: number;
collapse?: number;
transition?: number;
};
};
}
export interface ChatConfig {
api: ApiConfig;
assets: AssetsConfig;
layout?: LayoutConfig;
theme?: Partial<ChatTheme>;
content?: ContentConfig;
features?: FeaturesConfig;
closedView?: ClosedViewConfig;
advanced?: AdvancedConfig; // NEW: Optional namespace
// REMOVED: debug, markdownConfig, forceAnimations from root
}
```
**ConfigManager:**
```typescript
isDebugEnabled(): boolean {
return this.config.advanced?.debug?.enabled ?? false;
}
getMarkdownCdnUrls(): string[] | undefined {
return this.config.advanced?.markdown?.cdnUrls;
}
shouldForceAnimations(): boolean {
return this.config.advanced?.animations?.forceAnimations ?? false;
}
```
**Update Consumers:**
- `Logger.ts` - Use `advanced.debug.*`
- `MarkdownLoader.ts` - Use `advanced.markdown.*`
- `InputBarSheetController.ts` - Use `advanced.animations.forceAnimations`
**Deliverable:** Config consolidation COMPLETE! ✅
## Timeline
| Phase | Namespace | Duration | Cumulative |
|-------|-----------|----------|------------|
| 1 | API | 30 min | 30 min |
| 2 | Assets | 30 min | 1 hour |
| 3 | Layout | 45 min | 1h 45m |
| 4 | Theme Colors | 45 min | 2h 30m |
| 5 | Theme Toggle | 30 min | 3 hours |
| 6 | Theme InputBar | 30 min | 3h 30m |
| 7 | Content | 30 min | 4 hours |
| 8 | Features | 45 min | 4h 45m |
| 9 | Closed View | 20 min | 5h 05m |
| 10 | Advanced | 30 min | 5h 35m |
**Total Active Time:** ~6 hours
**Calendar Time:** 2-3 days (with testing and breaks)
## Final Structure (v3.0)
```typescript
interface ChatConfig {
// Required core
api: ApiConfig;
variant: ChatVariant;
containerId: string;
// Required namespaces
assets: AssetsConfig;
// Optional namespaces
layout?: LayoutConfig;
theme?: Partial<ChatTheme>;
content?: ContentConfig;
features?: FeaturesConfig;
closedView?: ClosedViewConfig;
advanced?: AdvancedConfig;
}
// Clean example
const widget = new ChatWidget({
api: { url: '...', token: '...' },
variant: 'corner',
containerId: 'chat',
assets: {
logo: '/logo.svg',
headerLogo: '/header.svg'
},
layout: {
desktop: { position: 'bottom-right', width: '400px' },
mobile: { height: '80vh' }
},
theme: {
colors: { text: '#000', background: '#fff' },
toggle: { background: '#1e1b4b', content: 'Chat' },
inputBar: { brandText: 'Ask AI' }
},
content: {
title: 'Support',
placeholder: 'Message...',
welcome: {
message: 'How can we help?',
prompts: ['Pricing', 'Features', 'Support']
}
},
features: {
attachments: { enabled: true },
streaming: { enabled: true },
persistence: { enabled: true }
},
closedView: { mode: 'welcome-card' },
advanced: {
debug: { enabled: true },
animations: { forceAnimations: true }
}
});
```
## Benefits
### Code Quality
- ✅ **Zero dead code** - No backward compatibility
- ✅ **Clean types** - No deprecated properties
- ✅ **Simple logic** - Direct property access
- ✅ **Better IntelliSense** - Grouped structure
### Developer Experience
- ✅ **Clear hierarchy** - 8 namespaces vs 35+ flat
- ✅ **Faster to learn** - Logical grouping
- ✅ **Type-safe** - Required vs optional clear
- ✅ **No confusion** - Single source of truth
### Maintenance
- ✅ **Easier to extend** - Add to namespace
- ✅ **Simpler testing** - Test by namespace
- ✅ **Better docs** - Natural structure
- ✅ **Cleaner PRs** - Focused changes
## Deployment
### Recommended: Branch-Based CDN
```bash
# v2.x on main branch
https://cdn.agentman.ai/chat-widget/v2/index.js
# v3.x on v3-clean-config branch
https://cdn.agentman.ai/chat-widget/v4/index.js
```
Users choose version by URL. Both versions maintained independently.
## Migration Guide
### v2.x → v3.0 Quick Reference
| v2.x Property | v3.0 Property |
|---------------|---------------|
| `apiUrl` | `api.url` |
| `agentToken` | `api.token` |
| `logo` | `assets.logo` |
| `headerLogo` | `assets.headerLogo` |
| `icons` | `assets.icons` |
| `position` | `layout.desktop.position` |
| `initialWidth` | `layout.desktop.width` |
| `initialHeight` | `layout.desktop.height` |
| `initiallyOpen` | `layout.desktop.initiallyOpen` |
| `mobileHeight` | `layout.mobile.height` |
| `mobileMaxHeight` | `layout.mobile.maxHeight` |
| `theme.textColor` | `theme.colors.text` |
| `theme.backgroundColor` | `theme.colors.background` |
| `theme.toggleBackgroundColor` | `theme.toggle.background` |
| `toggleText` | `theme.toggle.content` |
| `toggleStyle` | `theme.toggle` (duplicate removed) |
| `inputBarBrandText` | `theme.inputBar.brandText` |
| `title` | `content.title` |
| `placeholder` | `content.placeholder` |
| `initialMessage` | `content.initialMessage` |
| `messagePrompts` | `content.welcome` |
| `enableAttachments` | `features.attachments.enabled` |
| `streaming` | `features.streaming` |
| `persistence` | `features.persistence` |
| `collectClientMetadata` | `features.metadata.collectClientMetadata` |
| `agentClosedView` | `closedView.mode` |
| `debug` | `advanced.debug` |
| `markdownConfig` | `advanced.markdown` |
| `forceAnimations` | `advanced.animations.forceAnimations` |
*Document updated: 2025-01-12*
*Status: 🟢 Ready to Implement*
*Approach: Clean v3.0 with incremental phases (NO backward compatibility)*