574 lines
18 KiB
Markdown
574 lines
18 KiB
Markdown
# UI Redesign — Implementation Plan
|
||
|
||
*Branch: `ui-redesign` | Last updated: 2026-04-21*
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
Replace the basic Phase 6 single-page app (`web/static/`) with the enterprise-grade
|
||
migration console designed in `docs/ui-mockup/mockup.html`.
|
||
|
||
The backend is complete (Phases 8–13, 108/108 tests passing). All new UI phases are
|
||
**frontend-only** unless noted. Existing FastAPI routes do not change except where
|
||
noted under Phase 16 (readiness data) and Phase 19 (Verification API).
|
||
|
||
### Design reference
|
||
|
||
Open `docs/ui-mockup/mockup.html` in a browser to see all 8 screens before starting.
|
||
|
||
### Docusign 2024 brand tokens
|
||
|
||
| Token | Value | Usage |
|
||
|---|---|---|
|
||
| Cobalt | `#4C00FF` | Primary CTA, active nav highlight |
|
||
| Inkwell | `#130032` | Left nav background |
|
||
| Ecru | `#F8F3F0` | Page background |
|
||
| Poppy | `#FF5252` | Error / Blocked badge |
|
||
| Slate | `#6B6B9A` | Secondary text, muted labels |
|
||
| White | `#FFFFFF` | Card surfaces |
|
||
|
||
Typography: `Inter` (Google Fonts), fallback `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`.
|
||
|
||
---
|
||
|
||
## Current state
|
||
|
||
`web/static/` — three files, ~600 lines total:
|
||
- `index.html` — 79 lines, single-page layout (header, two panels, history table)
|
||
- `app.js` — 343 lines, vanilla JS (auth, template list, migrate, history)
|
||
- `style.css` — 186 lines, basic styles, non-Docusign colours
|
||
|
||
---
|
||
|
||
## File structure after redesign
|
||
|
||
Keep no-build-step approach (vanilla JS ES modules, no bundler). Split monolith into
|
||
logical files served statically by FastAPI.
|
||
|
||
```
|
||
web/static/
|
||
index.html # app shell (nav, router outlet, modals)
|
||
css/
|
||
tokens.css # CSS custom properties (brand colours, spacing)
|
||
base.css # reset, typography, utility classes
|
||
nav.css # left sidebar nav + top bar
|
||
cards.css # template cards, readiness badges
|
||
modals.css # dialog / modal styles
|
||
tables.css # history and audit tables
|
||
forms.css # settings form inputs
|
||
js/
|
||
state.js # global app state (project, auth, templates)
|
||
router.js # hash-based client-side router
|
||
api.js # thin fetch wrappers for all backend endpoints
|
||
auth.js # auth status, connect/disconnect, Adobe dialog
|
||
project.js # project switcher modal, project CRUD (localStorage)
|
||
templates.js # template list view, readiness badges, filters
|
||
migration.js # options modal, progress polling, results view
|
||
verification.js # send test envelope, poll status
|
||
history.js # history & audit view
|
||
settings.js # settings screen
|
||
utils.js # escHtml, formatDate, debounce, etc.
|
||
```
|
||
|
||
`app.js` and `style.css` are **deleted** (replaced by the above).
|
||
`index.html` is **rewritten** as the app shell.
|
||
|
||
---
|
||
|
||
## Phase 14 — App Shell & Navigation
|
||
|
||
**Goal:** Branded shell that all other views live inside. No functional logic yet —
|
||
just the frame, router, and state container.
|
||
|
||
### index.html structure
|
||
|
||
```html
|
||
<body>
|
||
<nav id="app-nav"> <!-- left sidebar, 220px, Inkwell bg -->
|
||
<div id="nav-logo">…</div> <!-- docusign SVG logo, white wordmark -->
|
||
<ul id="nav-links">…</ul> <!-- 7 nav links with icons -->
|
||
<div id="nav-project">…</div> <!-- project switcher footer -->
|
||
</nav>
|
||
<div id="app-body">
|
||
<header id="top-bar">…</header> <!-- breadcrumb + auth chips -->
|
||
<main id="router-outlet">…</main> <!-- views injected here -->
|
||
</div>
|
||
<!-- modal containers -->
|
||
<div id="modal-overlay" hidden>…</div>
|
||
</body>
|
||
```
|
||
|
||
### css/tokens.css
|
||
|
||
```css
|
||
:root {
|
||
--cobalt: #4C00FF;
|
||
--inkwell: #130032;
|
||
--ecru: #F8F3F0;
|
||
--poppy: #FF5252;
|
||
--slate: #6B6B9A;
|
||
--white: #FFFFFF;
|
||
--success: #28A745;
|
||
--warning: #F0A500;
|
||
--border: #E0DCF8;
|
||
--radius-sm: 4px;
|
||
--radius-md: 8px;
|
||
--shadow-sm: 0 1px 4px rgba(0,0,0,0.08);
|
||
--shadow-md: 0 4px 16px rgba(0,0,0,0.12);
|
||
}
|
||
```
|
||
|
||
### js/router.js
|
||
|
||
```js
|
||
const ROUTES = {
|
||
'#/dashboard': () => import('./templates.js').then(m => m.renderDashboard()),
|
||
'#/templates': () => import('./templates.js').then(m => m.renderTemplates()),
|
||
'#/results': () => import('./migration.js').then(m => m.renderResults()),
|
||
'#/issues': () => import('./issues.js').then(m => m.renderIssues()),
|
||
'#/verify': () => import('./verification.js').then(m => m.renderVerification()),
|
||
'#/history': () => import('./history.js').then(m => m.renderHistory()),
|
||
'#/settings': () => import('./settings.js').then(m => m.renderSettings()),
|
||
};
|
||
// Default route: #/templates
|
||
```
|
||
|
||
### js/state.js
|
||
|
||
```js
|
||
export const state = {
|
||
project: null, // { id, name } — loaded from localStorage
|
||
auth: { adobe: false, docusign: false },
|
||
templates: [], // array from /api/templates/status
|
||
selectedIds: new Set(),
|
||
lastMigrationResults: null, // results from most recent batch job
|
||
};
|
||
// Simple pub/sub: subscribe(key, fn) / publish(key)
|
||
```
|
||
|
||
### js/api.js — endpoint wrappers
|
||
|
||
All existing endpoints wrapped:
|
||
```js
|
||
export const api = {
|
||
auth: {
|
||
status: () => GET('/api/auth/status'),
|
||
connectAdobe: () => POST('/api/auth/adobe/connect'),
|
||
connectDocusign: () => POST('/api/auth/docusign/connect'),
|
||
exchangeAdobe: (url) => POST('/api/auth/adobe/exchange', { redirect_url: url }),
|
||
disconnect: (p) => POST(`/api/auth/${p}/disconnect`),
|
||
},
|
||
templates: {
|
||
status: () => GET('/api/templates/status'),
|
||
adobe: () => GET('/api/templates/adobe'),
|
||
docusign: () => GET('/api/templates/docusign'),
|
||
},
|
||
migrate: {
|
||
run: (body) => POST('/api/migrate', body),
|
||
batch: (body) => POST('/api/migrate/batch', body),
|
||
batchStatus: (id) => GET(`/api/migrate/batch/${id}`),
|
||
history: () => GET('/api/migrate/history'),
|
||
},
|
||
};
|
||
```
|
||
|
||
### js/utils.js
|
||
|
||
```js
|
||
export const escHtml = str => String(str).replace(/[&<>"]/g, c => …);
|
||
export const formatDate = iso => new Date(iso).toLocaleDateString(…);
|
||
export const formatRelative = iso => …;
|
||
export const debounce = (fn, ms) => { … };
|
||
export const uuid = () => crypto.randomUUID();
|
||
```
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-14): app shell — nav, router, state, brand tokens`
|
||
|
||
---
|
||
|
||
## Phase 15 — Project / Customer Context
|
||
|
||
**Goal:** Project switcher so the same installation can manage migrations for
|
||
multiple customers without mixing history or credentials.
|
||
|
||
### Data model (localStorage only — no backend)
|
||
|
||
```js
|
||
// Stored in localStorage key: 'migrator_projects'
|
||
{
|
||
active: "uuid-1",
|
||
projects: [
|
||
{ id: "uuid-1", name: "Acme Corp", createdAt: "2026-04-21T…" },
|
||
{ id: "uuid-2", name: "Globex Inc", createdAt: "2026-04-22T…" },
|
||
]
|
||
}
|
||
```
|
||
|
||
Credentials remain in the server-side signed cookie session. Switching projects
|
||
triggers a fresh `/api/auth/status` check (session may still be valid if the user
|
||
didn't disconnect).
|
||
|
||
### js/project.js
|
||
|
||
```js
|
||
export function listProjects() { … } // returns projects array
|
||
export function createProject(name) { … } // generates uuid, saves, returns project
|
||
export function deleteProject(id) { … }
|
||
export function getActive() { … } // returns active project or null
|
||
export function setActive(id) { … } // updates localStorage + triggers nav refresh
|
||
```
|
||
|
||
### Project switcher modal
|
||
|
||
- Opened by clicking project name in nav footer
|
||
- Lists projects: name + creation date + "Activate" button
|
||
- "New Project" inline form (name field + Create button)
|
||
- Deleting a project requires confirmation ("Delete Acme Corp? This cannot be undone.")
|
||
- First run: modal opens automatically with welcome copy
|
||
|
||
### Nav footer display
|
||
|
||
Shows `▸ Acme Corp` (truncated to 18 chars). Clicking opens switcher modal.
|
||
No project → shows `▸ New Project` in amber.
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-15): project switcher — localStorage CRUD, switcher modal`
|
||
|
||
---
|
||
|
||
## Phase 16 — Templates View with Readiness Badges
|
||
|
||
**Goal:** Replace the two-panel list with a filterable, sortable single table.
|
||
Each row shows a readiness badge computed from validation results.
|
||
|
||
### Readiness badge system
|
||
|
||
| Badge | Colour | Condition |
|
||
|---|---|---|
|
||
| Ready | `--success` green | `blockers=[]`, `warnings=[]` |
|
||
| Caveats | `--warning` amber | `blockers=[]`, `warnings.length > 0` |
|
||
| Blocked | `--poppy` red | `blockers.length > 0` |
|
||
| Migrated | `--cobalt` | `status=migrated` and no blockers |
|
||
| Needs Update | `--warning` amber | `status=needs_update` |
|
||
| Verified | green + ✓ | post-migration verification passed (Phase 19) |
|
||
|
||
### Backend update required: `web/routers/templates.py`
|
||
|
||
Add `blockers: list[str]` and `warnings: list[str]` to each template object in
|
||
`GET /api/templates/status`. Run `validate_template()` on the normalized form if the
|
||
template has been downloaded; otherwise return empty lists.
|
||
|
||
```python
|
||
# In templates.py status endpoint, for each adobe template:
|
||
normalized_dir = Path(settings.downloads_dir) / f"{template['name']}__{template['id']}"
|
||
if normalized_dir.exists():
|
||
normalized = adobe_folder_to_normalized(str(normalized_dir))
|
||
result = validate_template(normalized)
|
||
blockers = result.blockers
|
||
warnings = result.warnings
|
||
else:
|
||
blockers, warnings = [], []
|
||
```
|
||
|
||
Add 3 backend tests to `tests/test_api_templates.py`:
|
||
- `test_status_includes_blockers_and_warnings_fields`
|
||
- `test_status_blockers_populated_when_template_downloaded`
|
||
- `test_status_empty_when_not_downloaded`
|
||
|
||
### js/templates.js
|
||
|
||
```js
|
||
export function renderTemplates() {
|
||
// Fetches state.templates (or refreshes via api.templates.status())
|
||
// Renders filterable table into #router-outlet
|
||
// Columns: ☐ | Name | Readiness | Fields | Last Modified | DS Status | Actions
|
||
// Filter bar: search input + status dropdown + readiness dropdown
|
||
// Bulk toolbar (hidden until ≥1 selected): "Migrate X selected" button
|
||
}
|
||
|
||
export function renderTemplateDetail(adobeId) {
|
||
// 4-tab layout: Overview | Fields | Issues | Migration History
|
||
}
|
||
```
|
||
|
||
### Template detail view (`#/templates/:id`)
|
||
|
||
- **Overview tab:** name, description, roles, document count, last modified date
|
||
- **Fields tab:** table of fields — type, label, page, role, required, conditional
|
||
- **Issues tab:** blockers (red cards) + warnings (amber cards) from validation
|
||
- **Migration History tab:** records from `/api/migrate/history` filtered to this template
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-16): templates view — readiness badges, filters, detail tabs, backend blockers/warnings`
|
||
|
||
---
|
||
|
||
## Phase 17 — Migration Workflow UI
|
||
|
||
**Goal:** Options modal → progress view → results view as a cohesive flow.
|
||
|
||
### Flow
|
||
|
||
```
|
||
Templates view → select ≥1 template → "Migrate Selected" button →
|
||
Options modal → "Run Migration" →
|
||
Progress view (replaces modal) →
|
||
Results view (#/results)
|
||
```
|
||
|
||
### js/migration.js
|
||
|
||
```js
|
||
export function showOptionsModal(selectedIds) {
|
||
// Renders modal with:
|
||
// - Dry run toggle (default: off)
|
||
// - Overwrite existing toggle (default: off, from settings)
|
||
// - Include documents toggle (default: on, from settings)
|
||
// - Target folder text input (optional)
|
||
// - Selected count display
|
||
// - "Run Migration" button
|
||
}
|
||
|
||
export async function runMigration(ids, options) {
|
||
// Calls POST /api/migrate/batch
|
||
// Returns job_id
|
||
}
|
||
|
||
export async function pollJob(jobId, onProgress, onComplete) {
|
||
// Polls GET /api/migrate/batch/{jobId} every 2s
|
||
// Calls onProgress({ completed, total, results })
|
||
// Calls onComplete(finalResults) when status === 'done'
|
||
}
|
||
|
||
export function renderResults(jobResults) {
|
||
// Navigates to #/results and renders:
|
||
// - Summary row: X Created | Y Updated | Z Skipped | W Blocked | V Errors
|
||
// - Per-template result table
|
||
// - "Verify Templates" button (pre-loads migrated IDs)
|
||
// - "Back to Templates" button
|
||
// - "Export CSV" button (client-side Blob download)
|
||
}
|
||
```
|
||
|
||
### Progress view (inline, inside modal)
|
||
|
||
After "Run Migration" is clicked:
|
||
- Modal content replaces with: progress bar + per-template status list
|
||
- Each template row: name → ⏳ spinning → ✅ success or ❌ error
|
||
- "View Results" button appears when job status === 'done'
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-17): migration workflow — options modal, progress polling, results view`
|
||
|
||
---
|
||
|
||
## Phase 18 — Issues & Warnings View
|
||
|
||
**Goal:** A dedicated screen to review all validation problems before migrating.
|
||
|
||
### js/issues.js
|
||
|
||
```js
|
||
export function renderIssues() {
|
||
// Reads state.templates (already has blockers/warnings from Phase 16)
|
||
// Renders two sections:
|
||
// BLOCKERS — templates that will fail migration
|
||
// WARNINGS — templates that will migrate with caveats
|
||
// Each item: template name | issue message | suggested action link
|
||
// "Migrate Anyway" button on warning items → showOptionsModal([id])
|
||
// "View Template" link → #/templates/:id
|
||
}
|
||
```
|
||
|
||
### Nav badge
|
||
|
||
Left nav Issues link shows a red badge with count of blocked templates.
|
||
Updates whenever `state.templates` changes.
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-18): issues view — blocked and warning templates, nav badge`
|
||
|
||
---
|
||
|
||
## Phase 19 — Verification View
|
||
|
||
**Goal:** Send test envelopes to confirm migrated templates work end-to-end.
|
||
|
||
### New backend: `web/routers/verify.py`
|
||
|
||
```python
|
||
POST /api/verify/send
|
||
body: { template_id: str, recipient_name: str, recipient_email: str }
|
||
action: GET /v2.1/accounts/{id}/envelopes (create via template)
|
||
returns: { envelope_id: str }
|
||
|
||
GET /api/verify/status/{envelope_id}
|
||
action: GET /v2.1/accounts/{id}/envelopes/{envelopeId}
|
||
returns: { status: str, completed_at: str | null }
|
||
|
||
POST /api/verify/void/{envelope_id}
|
||
body: { reason: str }
|
||
action: PUT envelope status to "voided"
|
||
returns: { voided: true }
|
||
```
|
||
|
||
Register router in `web/app.py`: `app.include_router(verify_router, prefix="/api/verify")`.
|
||
|
||
### tests/test_api_verify.py
|
||
|
||
Four tests (all mock DocuSign calls with respx):
|
||
- `test_send_requires_auth`
|
||
- `test_send_returns_envelope_id`
|
||
- `test_status_returns_envelope_state`
|
||
- `test_void_calls_docusign`
|
||
|
||
### js/verification.js
|
||
|
||
```js
|
||
export function renderVerification(preloadedTemplateIds = []) {
|
||
// Shows list of migrated templates (from history or passed-in IDs)
|
||
// Per-template row:
|
||
// - Template name + DS template ID
|
||
// - "Send Test Envelope" button → opens send dialog
|
||
// - Status chip (Not Tested | Sent | Delivered | Completed = Verified | Voided)
|
||
// Send dialog: recipient name + email (pre-filled from settings), "Send" button
|
||
// After send: row updates with status, "Void" button, polling every 5s
|
||
}
|
||
```
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-19): verification view + verify API endpoints (send/status/void)`
|
||
|
||
---
|
||
|
||
## Phase 20 — History & Audit View
|
||
|
||
**Goal:** Filterable, exportable migration history.
|
||
|
||
### js/history.js
|
||
|
||
```js
|
||
export function renderHistory() {
|
||
// Calls GET /api/migrate/history
|
||
// Renders:
|
||
// - Filter bar: date range, template name search, status filter
|
||
// - Table: timestamp | template | action | status | DS ID | warnings | checksum
|
||
// - Expandable row: full blockers/warnings list, field count diff
|
||
// - "Export CSV" button (client-side)
|
||
}
|
||
```
|
||
|
||
SHA-256 checksum: first 8 chars displayed, full value in title attribute (tooltip).
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-20): history & audit view — filters, export, checksum display`
|
||
|
||
---
|
||
|
||
## Phase 21 — Settings View
|
||
|
||
**Goal:** Central config screen for verification defaults and migration defaults.
|
||
|
||
### Settings (localStorage key: `migrator_settings`)
|
||
|
||
| Key | Default | UI control |
|
||
|---|---|---|
|
||
| `testRecipientName` | `""` | Text input |
|
||
| `testRecipientEmail` | `""` | Email input |
|
||
| `autoVoidHours` | `24` | Number input |
|
||
| `defaultOverwrite` | `false` | Toggle |
|
||
| `defaultIncludeDocs` | `true` | Toggle |
|
||
|
||
### js/settings.js
|
||
|
||
```js
|
||
export function renderSettings() {
|
||
// 3 sections:
|
||
// 1. Verification defaults (name, email, auto-void timer)
|
||
// 2. Migration defaults (overwrite, include documents)
|
||
// 3. Connection info (read-only: connected accounts, base URLs)
|
||
// Save button writes to localStorage
|
||
// Values pre-loaded into options modal (Phase 17) and send dialog (Phase 19)
|
||
}
|
||
```
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-21): settings view — verification defaults, migration defaults`
|
||
|
||
---
|
||
|
||
## Phase 22 — Smoke Test Checklist & Cleanup
|
||
|
||
**Goal:** Validate the full redesigned UI works end-to-end, update docs.
|
||
|
||
### tests/UI-SMOKE-TEST.md
|
||
|
||
Manual checklist:
|
||
- [ ] First run: project switcher opens automatically
|
||
- [ ] Create project "Test Customer", verify it appears in nav footer
|
||
- [ ] Connect Adobe Sign via `.env` path → badge turns green
|
||
- [ ] Connect DocuSign via JWT path → badge turns green
|
||
- [ ] Templates view loads ≥1 template with correct readiness badge
|
||
- [ ] Select 2 templates → options modal opens → dry run → results show `dry_run` status
|
||
- [ ] Select 2 templates → real migration → progress bar counts up → results view
|
||
- [ ] Navigate to Verification → Send Test → status updates to Completed
|
||
- [ ] History view shows all migrations with correct counts and checksums
|
||
- [ ] Issues view shows blocked templates (use a fixture template with no recipients)
|
||
- [ ] Settings: save test recipient → reopen Settings → values persist
|
||
|
||
### Final tasks
|
||
|
||
- Run `pytest tests/ -v` — confirm all tests still pass (≥108 + new verify tests)
|
||
- Update `README.md` — new UI navigation guide section
|
||
- Update `docs/agent-harness/EXECUTION-BOARD.md` — Phases 14–22 complete
|
||
- Push `ui-redesign` branch to Gitea
|
||
- Open PR to `master`
|
||
|
||
### Commit
|
||
|
||
`feat(ui-phase-22): smoke test checklist, README update, final cleanup`
|
||
|
||
---
|
||
|
||
## Dependency order
|
||
|
||
```
|
||
Phase 14 (Shell)
|
||
└── Phase 15 (Project)
|
||
└── Phase 16 (Templates + backend readiness data)
|
||
├── Phase 17 (Migration workflow)
|
||
│ └── Phase 18 (Issues view)
|
||
└── Phase 19 (Verification + verify API)
|
||
|
||
Phase 20 (History) ← depends on Phase 14 only, can run after Phase 14
|
||
Phase 21 (Settings) ← depends on Phase 14 only, can run after Phase 14
|
||
|
||
Phase 22 (Cleanup) ← depends on all phases complete
|
||
```
|
||
|
||
Phases 20 and 21 can be implemented in parallel with Phases 17–19.
|
||
|
||
---
|
||
|
||
## What does NOT change
|
||
|
||
- All existing FastAPI routes (`auth.py`, `templates.py`, `migrate.py`)
|
||
- All backend Python source (`src/`)
|
||
- All 108 existing tests
|
||
- `.env` / credential handling
|
||
- The CLI pipeline (`src/migrate_template.py`)
|
||
|
||
Only backend additions:
|
||
1. **Phase 16:** `blockers` + `warnings` fields added to `GET /api/templates/status`
|
||
2. **Phase 19:** New `web/routers/verify.py` with 3 envelope endpoints
|