Frontend Architecture
The Lens frontend is a React 18 Single Page Application running inside an Atlassian Forge Custom UI iframe. It renders a hierarchical project portfolio grid powered by AG Grid, with drag-and-drop via dnd-kit, inline editing, Gantt timeline visualization, and a full inspector panel.
Technology Stack
Section titled “Technology Stack”| Layer | Technology | Version |
|---|---|---|
| UI Framework | React | 18.x |
| Grid | AG Grid Community | 35.x |
| Drag & Drop | @dnd-kit | 6.x |
| Routing | React Router | 6.x |
| i18n | react-intl (FormatJS) | 8.1.3 |
| Design System | @atlaskit/* (Atlassian Design System) | Various |
| Bridge | @forge/bridge | Latest |
Frontend Packages
Section titled “Frontend Packages”The app ships three independent frontend packages, each built with Create React App:
| Package | Path | Purpose |
|---|---|---|
| main | foundation/static/main/ | Primary app — grid, Gantt, inspector, inline editing, all features |
| admin | foundation/static/admin/ | Admin panel — feature flags, monitoring dashboard, CSS overrides |
| issue-context | foundation/static/issue-context/ | Jira issue context panel (displayed on individual issue pages) |
All three are registered as Forge Custom UI resources in manifest.yml and deployed together via forge deploy.
Entry Point & Boot Sequence
Section titled “Entry Point & Boot Sequence”index.tsx
Section titled “index.tsx”The React entry point mounts the app and enables the Forge theme:
import '@atlaskit/css-reset';import './foundation.css';import { view } from '@forge/bridge';
view.theme.enable(); // Sync ADS dark/light theme with Jira host
const root = ReactDOM.createRoot(document.getElementById('root'));root.render(<React.StrictMode><App /></React.StrictMode>);App.tsx
Section titled “App.tsx”App.tsx is the application shell. It:
- Creates a Forge bridge history object (
view.createHistory()) for SPA routing inside the iframe - Wraps the history object as a React Router v6
Navigator - Nests providers:
ErrorBoundary>IntlProvider>FeatureFlagProvider>RovoProvider - Mounts global components:
LicenseBanner,AnnouncementPopup,FeedbackButton - Registers routes
ErrorBoundary └── IntlProvider (i18n, lazy locale loading) └── FeatureFlagProvider (KVS-backed feature flags) └── RovoProvider (Rovo AI availability check) ├── LicenseBanner (license enforcement) ├── AnnouncementPopup (queued notices) ├── Router │ ├── / → LensListOrRedirect │ ├── /import → ImportHub (lazy) │ ├── /lens/:id → LensView │ ├── /structure/:id → StructureRedirect (legacy) │ └── /admin → AdminPage (lazy) └── FeedbackButtonBoot-time effects:
- Triggers
api.listLenses()early to surface OAuth consent dialog (QA-BUG-04) - Loads admin CSS overrides from KVS and injects them into
<head> LensListOrRedirectchecks for a last-viewed lens in KVS and auto-navigates
Routing
Section titled “Routing”Routes use Forge bridge history, not browser window.history. The bridge synchronizes the iframe URL with the Jira host URL bar.
| Route | Component | Description |
|---|---|---|
/ | LensListOrRedirect | Auto-redirects to last-viewed lens, or shows LensMenu |
/lens/:id | LensView | Main portfolio view (grid + Gantt + inspector) |
/import | ImportHub | Import hub (CSV, BigPicture, Structure, file) |
/admin | AdminPage | Admin dashboard (lazy-loaded) |
/structure/:id | StructureRedirect | Legacy bookmark redirect to /lens/:id |
The :id parameter supports both numeric IDs (e.g., 7) and friendly URL slugs (e.g., 7-q1-roadmap). LensView parses the numeric prefix via parseInt(rawId.split('-')[0], 10).
Context Providers
Section titled “Context Providers”FeatureFlagProvider
Section titled “FeatureFlagProvider”Loads feature flags from the backend on mount, merges with compile-time defaults. Exposes:
useFeatureFlags()hook — full flags object +setFlag()for admin paneluseFeatureFlag(name)hook — single boolean flagisFeatureEnabled(name)static function — for non-hook code (e.g., callbacks)
RovoProvider
Section titled “RovoProvider”Performs a single async check for Rovo AI availability via bridge.rovo?.isEnabled?.(). Fail-open: defaults to true if the bridge call is inconclusive. Provides:
useRovoEnabled()hook —null(loading),true, orfalse
API Client Pattern
Section titled “API Client Pattern”All backend communication goes through api/client.ts, which wraps @forge/bridge’s invoke():
import { invoke as rawInvoke } from '@forge/bridge';
function invoke<T>(functionKey: string, payload?: Record<string, any>): Promise<T> { return rawInvoke<T>(functionKey, payload).then((result: any) => { if (result?.error === 'license_inactive') { setLicenseInactive(); // Triggers license banner globally } return result; });}
export const api = { listLenses: () => invoke('listLenses', {}), getLensView: (id: string) => invoke('getLensView', { lensId: id }), updateField: (lensId, issueKey, field, value) => invoke('updateField', { ... }), // ... 60+ methods};The api object is the single point of contact between frontend and backend. Every method maps 1:1 to a Forge resolver function registered in src/index.ts.
Key interfaces exported from client.ts:
Lens— lens metadata (id, name, owner, mode, item_limit)HierarchyNode— tree node (id, parent_id, depth, position, node_type, issueData)CachedIssue— cached Jira issue fields (summary, status, assignee, priority, dates, custom_fields JSON)ViewConfig— saved view configuration (columns, formatting, density, Gantt settings, pipeline)FieldMeta— field metadata (id, name, type, isCustom)
State Management Philosophy
Section titled “State Management Philosophy”The app uses no state management library — all state lives in React’s built-in primitives:
useStatefor data and UI stateuseCallbackfor stable callback referencesuseMemofor derived datauseReffor values that should not trigger re-renders (editing state, timers, previous values)
LensView is the central state owner. See State Management for the complete data flow architecture.
Key Architectural Decisions
Section titled “Key Architectural Decisions”Server-Centric Data Assembly
Section titled “Server-Centric Data Assembly”The backend assembles complete views — the frontend never joins data from multiple API calls. getLensView() returns rows with enriched issueData already attached.
Controlled Component Pattern
Section titled “Controlled Component Pattern”HierarchyGrid (the AG Grid wrapper) is a fully controlled component. It receives nodes, columns, and callbacks as props. It never fetches its own data or owns persistent state.
No External State Store
Section titled “No External State Store”Rather than Redux or Zustand, state flows top-down from LensView through props and callbacks. This was a deliberate choice to keep the mental model simple and avoid synchronization bugs between a store and AG Grid’s internal state.
Forge Iframe Constraints
Section titled “Forge Iframe Constraints”The app runs in a cross-origin Forge iframe, which affects:
- Focus tracking —
stopEditingWhenCellsLoseFocusmust befalse - Popup positioning — AG Grid popups clip inside the iframe; editors use inline rendering
- Console.log — invisible in the parent page; debugging uses visible DOM elements
- Navigation —
view.createHistory()provides SPA routing within the iframe
Optimistic Updates
Section titled “Optimistic Updates”Inline edits update the local state immediately, then fire the API call. If the API call fails, the cell reverts and a toast notification appears.
File Organization
Section titled “File Organization”src/├── index.tsx # Entry point├── App.tsx # Provider tree + routing├── api/client.ts # Bridge API client (60+ methods)├── config/ # Feature flags, announcements config├── contexts/ # FeatureFlagContext, RovoContext├── pages/│ ├── LensList.tsx # Lens card grid (legacy)│ ├── LensMenu.tsx # Sidebar + preview lens list│ ├── LensView.tsx # Main view orchestrator│ └── lens-view/ # LensView sub-modules (12 custom hooks + sub-components)├── components/ # 26 feature directories (see components.md)├── hooks/ # 15 shared hooks├── utils/ # 38 utility modules├── types/ # Type definitions (density, pipeline, views, etc.)├── i18n/ # 11 locale files + IntlProvider├── foundation.css # All CSS (9000+ lines)└── __mocks__/ # Test mocks (@forge/bridge)Related Documentation
Section titled “Related Documentation”- Component Directory — all 26 component directories
- State Management — data flow, LensView state ownership, persistence
- Styling Guide — CSS conventions, density modes, AG Grid theming
- Inline Editing — AG Grid editors and Forge iframe workarounds
- Internationalization — react-intl setup, adding strings, locale support