Word Add-in
Microsoft Word task pane for clause search, pro-forma drafting, and context chat.Contents
Repository layout
office-addins/word/
├── assets/
├── docs/
│ └── sources.md
├── eslint.config.mjs
├── manifest.xml
├── package.json
├── tsconfig.json
├── webpack.config.js
├── src/
│ ├── taskpane.css
│ ├── taskpane.html
│ ├── taskpane.ts
│ ├── types.ts
│ ├── styles/
│ │ ├── base.css
│ │ ├── clauses.css
│ │ ├── chat.css
│ │ ├── feedback.css
│ │ ├── pro-forma.css
│ │ ├── shell.css
│ │ └── ...
│ ├── api/
│ │ ├── auth.ts
│ │ ├── chat.ts
│ │ ├── client.ts
│ │ ├── clauses.ts
│ │ ├── index.ts
│ │ └── pro-forma.ts
│ ├── shared/
│ │ ├── dom.ts
│ │ ├── errors.ts
│ │ ├── html.ts
│ │ ├── tabs.ts
│ │ └── toast.ts
│ ├── word/
│ │ ├── draft-format.ts
│ │ ├── host.ts
│ │ └── insert.ts
│ ├── clauses/
│ │ ├── detail.ts
│ │ ├── filters.ts
│ │ ├── grouped-results.ts
│ │ ├── index.ts
│ │ ├── modal.ts
│ │ ├── state.ts
│ │ └── types.ts
│ ├── pro-forma/
│ │ ├── draft-insert.ts
│ │ ├── index.ts
│ │ ├── profile-results.ts
│ │ ├── state.ts
│ │ └── status.ts
│ ├── chat/
│ │ ├── index.ts
│ │ ├── state.ts
│ │ ├── types.ts
│ │ └── view.ts
│ └── feedback/
│ ├── index.ts
│ ├── types.ts
│ └── view.ts
Run and verify
Prepare and run each side:
cd /home/axel/projects/hugo/source/main/office-addins/word
npm install
npx office-addin-dev-certs install
npm run build
npm run validate
npm run dev
For local end-to-end checks, run Hugo in one terminal and the add-in in another, then open:
cd /home/axel/projects/hugo/source/main
npx wrangler dev --local --local-protocol https --port 8788
cd /home/axel/projects/hugo/source/main/office-addins/word
npm run dev
https://localhost:3000/taskpane.html?api=https://localhost:8788
Authentication flow
Startup in office-addins/word/src/taskpane.ts calls ensureAccess(), then ping():
GET /api/users/me.
On 401 or network failure (most often status 0 when cookie flow is missing), the taskpane opens an Office dialog to /auth/addin-complete.
/auth/addin-complete is handled by registerWordAddinAuthRoutes and posts back to Office via Office.context.ui.messageParent():
{ "type": "hugo-addin-session", "token": "<short-lived JWT>", "expiresAt": "<ISO timestamp>" }
The token is stored in session storage under hugo_word_addin_session and sent on add-in API calls with Authorization: Bearer ....
Endpoint bridge
Word Web cannot reliably send CF Access cookies in the taskpane iframe. The client routes same-origin traffic through /addin/word-api/api/... with the bearer token.
Bridge behavior comes from registerWordAddinBridgeRoutes:
- Requires
authKind === 'addin_session'from the worker middleware. - Only allowlisted paths under
/api/...are forwarded to existing app handlers. /addin/word-feedbackand/addin/word-feedback/draftuse the same add-in token path but are intentionally separate from the generic API bridge.
Allowed bridge routes are exactly:
GET:/api/users/me,/api/funds,/api/clause-categories,/api/investor-categories,/api/fund-categories,/api/gps,/api/lp-countries,/api/investors,/api/clauses/search,/api/chat/status,/api/lp-profile/facets,/api/lp-profile,/api/clauses/:id,/api/funds/:fundId/clauses,/api/funds/:fundId/clauses/search.POST:/api/export-template/draft,/api/chat.
Tabs
- Cross-fund clause search with live filters.
- Filter semantics are OR within each facet and AND across facets.
- Results can be grouped by clause category.
- Insertion policy: append to
clauses-bodyif present; otherwise insert at current selection.
- Builds an LP context from sponsor/fund pickers, profile filters, and drafting fields.
- Loads profile options from
/api/lp-profile/facets. - Runs prevalences through
/api/lp-profile. - Submits
POST /api/export-template/draftwith eitherfund_id(fund scope) orsponsor_id(sponsor scope).
- Enabled only when
GET /api/chat/statusreportsenabled. - Loads accessible funds and optional scope, then posts turns to
POST /api/chat. - Shows returned clauses as cards with one-click insert/copy actions.
Insertion semantics
Clause insertion
Clause text insertion from search and chat uses:
selection.insertText(..., Word.InsertLocation.replace)when noclauses-bodyanchor exists.- When an existing
clauses-bodycontent control is found, it inserts paragraphs directly into that anchor.
Pro-forma insertion
- Desktop Word inserts via
selection.insertFileFromBase64. - Word Web inserts via
selection.insertOoxmland requests?format=ooxml. - After insertion, the add-in writes comparable clauses into the inserted template’s
clauses-bodycontrol. - Heading and body style hints from
heading2_styleandnormal_styleare applied as direct formatting.
Pro-forma flow (server + add-in)
- Resolution: the draft endpoint resolves template with fund override -> sponsor default -> built-in starter (or sponsor-only default when no fund is selected).
- Render: it renders template data with empty clause loop, normalizing a
clauses-bodyanchor. - Response: payload includes
{ format, body, filename, source, heading2_style, normal_style }, where format isdocxorooxml. - Insert: desktop inserts DOCX from base64; web inserts OOXML.
- Post-insert: comparable clause cards are formatted and appended into the content control when available.
Chat architecture
The backend uses one clause tool plan per request. Supported modes are:
examples→ clause cards for insertion/copy.count→ count summary.breakdown→ grouped aggregate result.
Example response shape:
{
assistant: string,
clauses?: ChatClauseCard[],
meta: { mode: 'examples' | 'count' | 'breakdown'; result_count: number; latency_ms: number }
}
Feedback
A floating widget posts via POST /addin/word-feedback and loads draft state from GET /addin/word-feedback/draft. Both routes are associated with the shared route template /addin/word/taskpane in admin feedback handling.
Endpoints used
| Method | Path | Use |
|---|---|---|
| GET | /api/users/me | Auth probe / reauth path. |
| GET | /api/funds | Populate filters and scopes. |
| GET | /api/clause-categories | Clause facets. |
| GET | /api/investor-categories | Investor facets. |
| GET | /api/fund-categories | Fund facets. |
| GET | /api/gps | Sponsor facets. |
| GET | /api/lp-countries | Investor country facets. |
| GET | /api/investors | Investor entities for the LP form. |
| GET | /api/clauses/search | Cross-fund search/count/aggregate. |
| GET | /api/clauses/:id | Clause detail. |
| GET | /api/funds/:fundId/clauses | Fund-scoped clause list. |
| GET | /api/funds/:fundId/clauses/search | Fund-scoped clause search. |
| GET | /api/lp-profile/facets | Pro forma profile filter options. |
| GET | /api/lp-profile | Prevalence query for the selected LP profile. |
| POST | /api/export-template/draft | Render template draft for Word insertion. |
| GET | /api/chat/status | Probe chat availability and provider metadata. |
| POST | /api/chat | Run one-tool query with clause-aware output. |
| POST | /addin/word-feedback | Taskpane feedback create/update. |
| GET | /addin/word-feedback/draft | Load existing taskpane feedback draft. |
| any | /addin/word-api/api/* | Word Web same-origin bridge entrypoint. |
Known limits
- Chat is intentionally narrow and single-tool today.
- Clause tab inserts remain plain text.
- Word Web pro-forma insertion depends on OOXML-friendly templates.