Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
318
admin-ui/js/stores/context-store.js
Normal file
318
admin-ui/js/stores/context-store.js
Normal file
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* @fileoverview A simple global store for application context.
|
||||
* Manages shared state like current page, project, team, and selected items.
|
||||
* Used by components like ds-ai-chat to be context-aware.
|
||||
* MVP2: Extended with admin settings and project configuration context.
|
||||
*
|
||||
* ARCHITECTURE NOTE: Context store uses localStorage directly for persistence.
|
||||
* Incognito mode handling is managed at the API client layer (api-client.js).
|
||||
* This removes circular dependencies and initialization order issues.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page-specific context prompts for the AI assistant.
|
||||
* These provide contextual awareness for each page in the admin UI.
|
||||
*/
|
||||
const PAGE_CONTEXT_PROMPTS = {
|
||||
dashboard: 'You are helping with the DSS dashboard. The user can see project overview, recent activity, and quick actions.',
|
||||
projects: 'You are helping manage design system projects. The user can create, configure, and monitor projects.',
|
||||
services: 'You are helping analyze design system services and integrations.',
|
||||
'quick-wins': 'You are helping find quick win opportunities for design system adoption.',
|
||||
chat: 'You are a helpful AI assistant for the Design System Server.',
|
||||
tokens: 'You are helping manage design tokens. The user can extract, validate, sync, and transform tokens between formats.',
|
||||
components: 'You are helping with React components. The user can generate, audit, and validate design system components.',
|
||||
figma: 'You are helping with Figma integration. The user can connect files, sync tokens, and view visual diffs.',
|
||||
docs: 'You are helping with DSS documentation and guides.',
|
||||
teams: 'You are helping manage team access and permissions for design system projects.',
|
||||
audit: 'You are helping with design system audits. The user can identify issues, track adoption, and create quick wins.',
|
||||
plugins: 'You are helping manage DSS plugins and extensions.',
|
||||
settings: 'You are helping configure DSS settings and preferences.',
|
||||
storybook: 'You are helping with Storybook setup. The user can generate stories and configure theme documentation.',
|
||||
};
|
||||
|
||||
class ContextStore extends EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
// MVP1 Core Context (no defaults for project - force selection)
|
||||
projectId: localStorage.getItem('current_project_id') || null,
|
||||
teamId: localStorage.getItem('current_team_id') || 'ui',
|
||||
userId: localStorage.getItem('current_user_id') || 'admin',
|
||||
capabilities: JSON.parse(localStorage.getItem('user_capabilities') || '[]'),
|
||||
|
||||
// MVP2: Admin settings context
|
||||
adminSettings: {
|
||||
hostname: localStorage.getItem('admin_hostname') || 'localhost',
|
||||
port: parseInt(localStorage.getItem('admin_port')) || 6006,
|
||||
isRemote: localStorage.getItem('admin_isRemote') === 'true' || false,
|
||||
dssSetupType: localStorage.getItem('admin_setupType') || 'local',
|
||||
},
|
||||
|
||||
// MVP2: Current project configuration
|
||||
currentProject: null, // Will be set from ProjectStore
|
||||
currentProjectSkin: localStorage.getItem('current_project_skin') || 'default',
|
||||
|
||||
// Legacy fields (keep for backward compatibility)
|
||||
page: null,
|
||||
project: null,
|
||||
team: 'all',
|
||||
selectedItems: [], // For batch actions on lists
|
||||
pageData: null, // Current page-specific data (tokens, components, etc.)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one or more context values and dispatches an event.
|
||||
* @param {Partial<typeof this.state>} newContext - An object with keys to update.
|
||||
*/
|
||||
setContext(newContext) {
|
||||
let hasChanged = false;
|
||||
const changes = {};
|
||||
|
||||
for (const key in newContext) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.state, key)) {
|
||||
const oldValue = this.state[key];
|
||||
const newValue = newContext[key];
|
||||
|
||||
// Deep comparison for arrays/objects
|
||||
const isDifferent = Array.isArray(newValue) || typeof newValue === 'object'
|
||||
? JSON.stringify(oldValue) !== JSON.stringify(newValue)
|
||||
: oldValue !== newValue;
|
||||
|
||||
if (isDifferent) {
|
||||
this.state[key] = newValue;
|
||||
changes[key] = { oldValue, newValue };
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanged) {
|
||||
this.dispatchEvent(new CustomEvent('context-change', {
|
||||
detail: {
|
||||
state: { ...this.state },
|
||||
changes
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a single context value
|
||||
* @param {string} key - The context key
|
||||
* @param {any} value - The new value
|
||||
*/
|
||||
set(key, value) {
|
||||
this.setContext({ [key]: value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single context value
|
||||
* @param {string} key - The context key
|
||||
* @returns {any}
|
||||
*/
|
||||
get(key) {
|
||||
return this.state[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state (shallow copy).
|
||||
* @returns {typeof this.state}
|
||||
*/
|
||||
getState() {
|
||||
return { ...this.state };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a context-aware prompt for the current page.
|
||||
* Used by the AI chat to provide page-specific assistance.
|
||||
* @returns {string} A contextual prompt string.
|
||||
*/
|
||||
getContextPrompt() {
|
||||
const page = this.get('page') || 'dashboard';
|
||||
return PAGE_CONTEXT_PROMPTS[page] || PAGE_CONTEXT_PROMPTS.chat;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to subscribe to context changes.
|
||||
* @param {Function} callback - The function to call on change.
|
||||
* @returns {Function} An unsubscribe function.
|
||||
*/
|
||||
subscribe(callback) {
|
||||
const handler = (event) => callback(event.detail);
|
||||
this.addEventListener('context-change', handler);
|
||||
// Return an unsubscribe function
|
||||
return () => this.removeEventListener('context-change', handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to changes on a specific key only
|
||||
* @param {string} key - The key to watch
|
||||
* @param {Function} callback - Called with (newValue, oldValue) when key changes
|
||||
* @returns {Function} Unsubscribe function
|
||||
*/
|
||||
subscribeToKey(key, callback) {
|
||||
const handler = (event) => {
|
||||
const { changes } = event.detail;
|
||||
if (changes[key]) {
|
||||
callback(changes[key].newValue, changes[key].oldValue);
|
||||
}
|
||||
};
|
||||
this.addEventListener('context-change', handler);
|
||||
return () => this.removeEventListener('context-change', handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears selection state
|
||||
*/
|
||||
clearSelection() {
|
||||
this.setContext({ selectedItems: [] });
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to selection
|
||||
* @param {Array} items - Items to add
|
||||
*/
|
||||
addToSelection(items) {
|
||||
const current = this.state.selectedItems || [];
|
||||
const newItems = Array.isArray(items) ? items : [items];
|
||||
this.setContext({
|
||||
selectedItems: [...new Set([...current, ...newItems])]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes items from selection
|
||||
* @param {Array} items - Items to remove
|
||||
*/
|
||||
removeFromSelection(items) {
|
||||
const current = this.state.selectedItems || [];
|
||||
const toRemove = new Set(Array.isArray(items) ? items : [items]);
|
||||
this.setContext({
|
||||
selectedItems: current.filter(item => !toRemove.has(item))
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP1: Set active project with storage persistence
|
||||
* @param {string} id - Project ID
|
||||
*/
|
||||
setProject(id) {
|
||||
if (!id) {
|
||||
console.error('[ContextStore] Cannot set null projectId');
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('current_project_id', id);
|
||||
this.setContext({ projectId: id, project: id }); // Update both for compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP1: Set active team with storage persistence
|
||||
* @param {string} id - Team ID (ui, ux, qa, admin)
|
||||
*/
|
||||
setTeam(id) {
|
||||
localStorage.setItem('current_team_id', id);
|
||||
this.setContext({ teamId: id, team: id }); // Update both for compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP1: Check if project is selected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasProject() {
|
||||
return !!this.state.projectId;
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP1: Get full context for MCP tool calls
|
||||
* @returns {Object} Context object with projectId, teamId, userId
|
||||
*/
|
||||
getMCPContext() {
|
||||
return {
|
||||
project_id: this.state.projectId,
|
||||
team_id: this.state.teamId,
|
||||
user_id: this.state.userId,
|
||||
capabilities: this.state.capabilities,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Update admin settings in context
|
||||
* @param {Object} settings - Admin settings object
|
||||
*/
|
||||
updateAdminSettings(settings) {
|
||||
this.setContext({
|
||||
adminSettings: {
|
||||
...this.state.adminSettings,
|
||||
...settings
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Get current admin settings
|
||||
* @returns {Object} Admin settings
|
||||
*/
|
||||
getAdminSettings() {
|
||||
return { ...this.state.adminSettings };
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Update current project information
|
||||
* @param {Object} project - Project object from ProjectStore
|
||||
*/
|
||||
setCurrentProject(project) {
|
||||
this.setContext({
|
||||
currentProject: project,
|
||||
projectId: project?.id || null,
|
||||
currentProjectSkin: project?.skinSelected || 'default'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Get current project from context
|
||||
* @returns {Object|null} Current project or null
|
||||
*/
|
||||
getCurrentProject() {
|
||||
return this.state.currentProject || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Update current project skin
|
||||
* @param {string} skin - Skin name
|
||||
*/
|
||||
setCurrentProjectSkin(skin) {
|
||||
localStorage.setItem('current_project_skin', skin);
|
||||
this.setContext({
|
||||
currentProjectSkin: skin
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Check if system is configured for remote/headless mode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isRemoteSetup() {
|
||||
return this.state.adminSettings.isRemote;
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Check if system is configured for local mode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLocalSetup() {
|
||||
return !this.state.adminSettings.isRemote;
|
||||
}
|
||||
|
||||
/**
|
||||
* MVP2: Get DSS setup type
|
||||
* @returns {string} 'local' or 'remote'
|
||||
*/
|
||||
getDSSSetupType() {
|
||||
return this.state.adminSettings.dssSetupType;
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
const contextStore = new ContextStore();
|
||||
export default contextStore;
|
||||
Reference in New Issue
Block a user