docs: add cool new plans

This commit is contained in:
Johannes Millan 2025-12-07 13:41:55 +01:00
parent e8258940cb
commit 648ce47249
2 changed files with 490 additions and 0 deletions

View file

@ -0,0 +1,295 @@
# Evaluation: Migrating Issue Providers to Plugin System
## Summary
**Goals**: Enable community providers, reduce core bundle size, hybrid UI (app controls standard UI, plugins can extend), full feature parity including Jira worklogs/transitions.
**Verdict**: Feasible but significant effort. The hybrid approach is the right strategy - it preserves native UX while allowing plugin flexibility.
---
## Complexity Assessment
| Aspect | Complexity | Notes |
| ----------------------------- | ---------- | ------------------------------------ |
| Plugin API extension | Medium | ~8 new methods needed |
| State management | Low | Extend existing IssueProvider model |
| Config UI | Medium | JSON Schema to Formly conversion |
| Issue content display | Low | Plugin provides config, app renders |
| Add-task bar search | Medium | Route searches to plugin providers |
| Polling infrastructure | Low | App already handles timers/batching |
| Custom actions (worklogs etc) | High | New UI integration points needed |
| Bundle size reduction | Medium | Requires lazy loading infrastructure |
| Migration (9 providers) | High | Each ~1-2 weeks to convert |
**Overall: Medium-High complexity**
---
## Architecture: Hybrid Plugin Issue Provider
```
┌─────────────────────────────────────────────────────────────┐
│ APP (Angular Core) │
├─────────────────────────────────────────────────────────────┤
│ • IssueProviderRegistry - tracks plugin providers │
│ • Config UI - renders JSON Schema as native forms │
│ • Add-task bar - searches plugin providers │
│ • Issue content display - renders from plugin config │
│ • Polling - app-controlled timers, dispatches to plugins │
│ • NgRx state - stores provider configs (built-in + plugin) │
│ • Custom action menu - shows plugin-registered actions │
└────────────────────────┬────────────────────────────────────┘
│ PluginAPI.issueProvider.*
┌─────────────────────────────────────────────────────────────┐
│ PLUGIN (JavaScript) │
├─────────────────────────────────────────────────────────────┤
│ • Register provider manifest (capabilities, config schema) │
│ • Handle requests (search, getById, refresh, testConnection)│
│ • Define issue field display configuration │
│ • Register custom actions (worklogs, transitions) │
│ • Open custom dialogs for advanced workflows │
└─────────────────────────────────────────────────────────────┘
```
---
## New PluginAPI Methods Required
```typescript
// Registration
registerIssueProvider(manifest: IssueProviderManifest): void;
// Request handling (plugin implements this)
onIssueProviderRequest(
handler: (request: IssueProviderRequest) => Promise<IssueProviderResponse>
): void;
// Custom actions for advanced features
registerIssueAction(action: IssueCustomAction): void;
onIssueAction(actionId: string, handler: (ctx: ActionContext) => Promise<void>): void;
// Custom dialogs (iFrame-based for complex UI like worklogs)
openIssueDialog(config: IssueDialogConfig): Promise<unknown>;
// Config access
getIssueProviderConfig(providerId: string): Promise<Record<string, unknown>>;
```
### IssueProviderManifest Structure
```typescript
interface IssueProviderManifest {
providerKey: string; // e.g., 'PLUGIN_LINEAR'
displayName: string;
icon: string; // SVG or Material icon
capabilities: {
search: boolean;
polling: boolean;
backlogImport: boolean;
updateFromTask: boolean; // sync task changes back
};
configSchema: JSONSchema7; // App renders as native form
pollInterval?: number; // ms, 0 = no polling
issueContentConfig: {
// How to display issue details
fields: IssueFieldConfig[];
comments?: CommentConfig;
};
customActions?: IssueCustomAction[]; // Worklogs, transitions, etc.
}
```
---
## How Advanced Features Work
### Example: Jira Worklog
1. **Plugin registers action:**
```javascript
api.registerIssueProvider({
providerKey: 'PLUGIN_JIRA',
customActions: [
{
id: 'jira-worklog',
label: 'Log Work',
icon: 'schedule',
placement: ['taskMenu', 'issuePanel'],
},
],
// ...
});
```
2. **User clicks "Log Work" in task menu**
3. **App calls plugin's action handler:**
```javascript
api.onIssueAction('jira-worklog', async (ctx) => {
// Open iFrame dialog with worklog form
const result = await api.openIssueDialog({
title: `Log Work - ${ctx.issue.key}`,
iframeUrl: 'worklog.html',
data: { task: ctx.task, issue: ctx.issue },
});
if (result?.submitted) {
// Submit to Jira API, then refresh
await submitWorklog(result.data);
api.dispatchAction({ type: '[Task] Refresh Issue', taskId: ctx.task.id });
}
});
```
4. **Plugin's `worklog.html` renders the form in iFrame**
---
## What App Controls vs What Plugin Controls
| Responsibility | App | Plugin |
| --------------------- | -------------------------- | ----------------------------- |
| Config form UI | JSON Schema -> native form | Provides schema |
| Issue list/search UI | Renders search results | Returns data |
| Issue content display | Renders fields | Provides field config |
| Polling timing | Manages timers | Handles refresh requests |
| State persistence | NgRx store + sync | Provider config stored by app |
| Custom action menu | Shows action buttons | Registers actions + handlers |
| Custom dialogs | Hosts iFrame | Provides iFrame content |
---
## Implementation Phases
### Phase 1: Plugin API Foundation
- Add `registerIssueProvider()` and `onIssueProviderRequest()` to PluginAPI
- Create `PluginIssueProviderRegistry` service
- Create `PluginIssueProviderProxy` implementing `IssueServiceInterface`
- Modify `IssueService` to route to plugin providers
- Extend `IssueProvider` model with `pluginConfig` field
**Files to modify:**
- `src/app/plugins/plugin-api.ts`
- `src/app/plugins/plugin-bridge.service.ts`
- `src/app/features/issue/issue.service.ts`
- `src/app/features/issue/issue.model.ts`
**Files to create:**
- `src/app/plugins/issue-provider/plugin-issue-provider.registry.ts`
- `src/app/plugins/issue-provider/plugin-issue-provider.proxy.ts`
- `packages/plugin-api/src/issue-provider.ts` (types)
### Phase 2: Core Integration
- Add plugin provider search to add-task bar
- Support plugin `issueContentConfig` in issue display component
- Create config dialog that renders JSON Schema
- Integrate plugin providers into polling effects
**Files to modify:**
- `src/app/features/tasks/add-task-bar/add-task-bar-issue-search.service.ts`
- `src/app/features/issue/issue-content/issue-content.component.ts`
- `src/app/features/issue/store/poll-issue-updates.effects.ts`
### Phase 3: Custom Actions
- Add `registerIssueAction()` and `onIssueAction()` to PluginAPI
- Create UI for showing plugin actions in task context menu
- Implement `openIssueDialog()` for iFrame-based custom dialogs
**Files to modify:**
- `src/app/features/tasks/task-context-menu/task-context-menu.component.ts`
- `src/app/features/issue/issue-content/issue-content.component.ts`
**Files to create:**
- `src/app/plugins/ui/plugin-issue-dialog/plugin-issue-dialog.component.ts`
### Phase 4: Sample Plugin + Documentation
- Create sample plugin (e.g., Linear or simple GitHub clone)
- Write plugin development guide
- Test full workflow
### Phase 5: Migrate Built-in Providers (optional, per-provider)
- Convert providers to plugin format
- Add lazy loading for bundle reduction
- Keep built-in as fallback during transition
---
## Advantages
1. **Community providers**: Anyone can create a provider without forking the app
2. **Decoupled releases**: Provider updates independent of app releases
3. **Reduced bundle** (Phase 5): ~1.2MB of provider code can be lazy-loaded
4. **Consistent architecture**: Single plugin system for all extensions
5. **User choice**: Install only providers you need
## Disadvantages
1. **Development effort**: Significant upfront work
2. **Dual maintenance**: During transition, maintain both systems
3. **Plugin dialog UX**: iFrame dialogs won't match app styling perfectly
4. **Type safety**: Plugin code is JavaScript, loses TypeScript benefits
5. **Performance**: Small overhead from plugin communication
---
## Risks & Mitigations
| Risk | Mitigation |
| -------------------------------- | -------------------------------------------- |
| Plugin dialogs feel disconnected | Pass theme CSS vars, provide styling guide |
| Breaking existing configs | Keep built-in providers, migration tool |
| Plugin security (credentials) | Configs stored in app, passed per-request |
| Performance overhead | Batch requests, cache in registry |
| Complex provider migration | Start with simple providers (Gitea, Redmine) |
---
## Effort Estimate
| Phase | Scope | Effort |
| ------- | ---------------------- | -------------- |
| Phase 1 | Plugin API foundation | 1-2 weeks |
| Phase 2 | Core integration | 1-2 weeks |
| Phase 3 | Custom actions | 1 week |
| Phase 4 | Sample plugin + docs | 1 week |
| Phase 5 | Per-provider migration | 1-2 weeks each |
**Total for enabling plugin providers**: ~4-6 weeks
**Per built-in provider migration**: ~1-2 weeks each (9 providers = 9-18 weeks if all migrated)
---
## Recommendation
**Start with Phases 1-4** to enable community providers. This gives you:
- Community can create new providers
- Proves the architecture works
- ~4-6 weeks of work
**Phase 5 (migration)** can be done incrementally or skipped:
- Built-in providers continue working
- Migrate only if bundle size becomes critical
- Start with simple providers (Gitea, Redmine)
- Keep Jira built-in longest (most complex)
This approach gets value quickly while keeping risk low.

View file

@ -0,0 +1,195 @@
# Plan: Plugin UI Consistency via CSS Library + Reactive Theme
## Goal
Make iframe plugin UI more consistent with the main app by:
1. Providing a shared CSS component library
2. Adding reactive theme updates when user switches dark/light mode
## Approach
CSS-only component library (minimal: 5-7 components) + reactive theme hook
---
## Part 1: Reactive Theme Updates
### Changes Required
**1. Add `THEME_CHANGE` hook**
- File: `src/app/plugins/plugin-api.model.ts`
- Add `THEME_CHANGE = 'themeChange'` to `PluginHooks` enum
**2. Emit theme change events to plugins**
- File: `src/app/plugins/plugin-bridge.service.ts`
- Subscribe to `GlobalThemeService.darkMode()` signal
- When theme changes, call all registered `themeChange` hook handlers
- Also post `THEME_UPDATE` message to all active plugin iframes with new CSS variables
**3. Add message handler in iframe for CSS variable updates**
- File: `src/app/plugins/util/plugin-iframe.util.ts`
- Add new message type `THEME_UPDATE` to handle dynamic CSS variable injection
- In `createPluginApiScript()`, add listener that updates `:root` CSS variables
**4. Update plugin API types**
- File: `packages/plugin-api/src/index.ts`
- Add `THEME_UPDATE` to `PluginIframeMessageType`
- Export `THEME_CHANGE` hook type
---
## Part 2: CSS Component Library
### Components to Include (Minimal Set)
1. **Buttons** - `.btn`, `.btn-primary`, `.btn-outline`, `.btn-icon`
2. **Cards** - `.card`, `.card-header`, `.card-content`
3. **Inputs** - `.input`, `.textarea`, `.select`
4. **Checkbox/Toggle** - `.checkbox`, `.toggle`
5. **Text utilities** - `.text-muted`, `.text-primary`, `.text-sm`
6. **Layout helpers** - `.flex`, `.gap`, `.stack` (vertical stack)
7. **Lists** - `.list`, `.list-item`
> **No prefix** - keeps classes simple. Plugins are isolated in iframes so no conflict risk.
### Implementation
**1. Create CSS library file**
- File: `src/assets/plugin-components.css`
- Define all component classes using existing CSS variables
- Match Angular Material visual style (border-radius, shadows, colors, etc.)
**2. Inject CSS library into plugin iframes**
- File: `src/app/plugins/util/plugin-iframe.util.ts`
- Modify `createPluginCssInjection()` to include the component library CSS
- Alternatively, inline the CSS directly (avoids external fetch issues in blob URLs)
**3. Document for plugin developers**
- Add documentation/examples in `packages/plugin-dev/`
- Update one example plugin to demonstrate usage
---
## Files to Modify
| File | Changes |
| -------------------------------------------- | ----------------------------------------- |
| `src/app/plugins/plugin-api.model.ts` | Add `THEME_CHANGE` hook |
| `packages/plugin-api/src/index.ts` | Add `THEME_UPDATE` message type |
| `src/app/plugins/plugin-bridge.service.ts` | Emit theme change events |
| `src/app/plugins/util/plugin-iframe.util.ts` | Handle `THEME_UPDATE`, inject CSS library |
| `src/assets/plugin-components.css` | **New file** - CSS component library |
---
## Implementation Order
1. Add `THEME_UPDATE` message type to plugin API package
2. Create `plugin-components.css` with minimal components
3. Update `createPluginCssInjection()` to inject the CSS library
4. Add `THEME_CHANGE` hook type
5. Implement theme change detection and broadcasting in `plugin-bridge.service.ts`
6. Add message handler in iframe script for updating CSS variables
7. Test with existing plugin (e.g., procrastination-buster)
---
## CSS Component Library Design
```css
/* Example structure for plugin-components.css */
/* Buttons */
.btn {
padding: var(--s-half) var(--s);
border-radius: 4px;
border: 1px solid var(--extra-border-color);
background: transparent;
color: var(--text-color);
cursor: pointer;
font-family: inherit;
font-size: inherit;
transition: var(--transition-standard);
}
.btn:hover {
border-color: var(--c-primary);
}
.btn-primary {
background: var(--c-primary);
border-color: var(--c-primary);
color: white;
}
.btn-primary:hover {
filter: brightness(1.1);
}
/* Cards */
.card {
background: var(--card-bg);
border-radius: var(--card-border-radius);
box-shadow: var(--card-shadow);
padding: var(--s2);
}
/* Inputs */
.input,
.textarea,
.select {
padding: var(--s-half) var(--s);
border-radius: 4px;
border: 1px solid var(--extra-border-color);
background: var(--bg);
color: var(--text-color);
font-family: inherit;
font-size: inherit;
}
.input:focus,
.textarea:focus,
.select:focus {
outline: none;
border-color: var(--c-primary);
}
/* Text utilities */
.text-muted {
color: var(--text-color-muted);
}
.text-primary {
color: var(--c-primary);
}
.text-sm {
font-size: 0.875rem;
}
/* Layout */
.stack {
display: flex;
flex-direction: column;
gap: var(--s);
}
.flex {
display: flex;
gap: var(--s);
}
```
---
## Notes
- CSS library is opt-in (plugins can use it or ignore it)
- Existing plugins continue to work (backward compatible)
- Theme updates happen automatically via postMessage
- Plugins can also manually listen to `THEME_CHANGE` hook for custom handling