18 KiB
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
<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
: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
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
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:
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
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)
// 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
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.
# 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_fieldstest_status_blockers_populated_when_template_downloadedtest_status_empty_when_not_downloaded
js/templates.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/historyfiltered 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
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
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
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_authtest_send_returns_envelope_idtest_status_returns_envelope_statetest_void_calls_docusign
js/verification.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
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
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
.envpath → 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_runstatus - 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-redesignbranch 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:
- Phase 16:
blockers+warningsfields added toGET /api/templates/status - Phase 19: New
web/routers/verify.pywith 3 envelope endpoints