# Changelog All notable changes to **FORGE** are documented here. Format based on [Keep a Changelog](https://keepachangelog.com/); this project uses [Semantic Versioning](https://semver.org/). ## [2.3.2] — 2026-08-29 ### Changed - **PM Insights — full-width category items.** Inside **Alert categories** and **Watch list**, each category is now a **full-width accordion** stacked in a single column (was a narrow multi-column grid), with more breathing room: larger inter-card gap, roomier card headers and ticket rows, and a taller expanded list. The click-to- expand behaviour per category is unchanged. (CSS-only: the `.grid` becomes a flex column; `.cardHead`/`.row` padding widened.) ## [2.3.1] — 2026-08-29 ### Changed - **PM Insights — collapsible sections.** The **Alert categories** and **Watch list** sections are now accordions: each heading is a button (chevron + a category-count badge, `aria-expanded`/`aria-controls`) that collapses/expands its card grid. Both default to open; the per-card ticket expand and the **Per-PM roll-up** table are unchanged. Adds a `ChevronIcon` (rotates with the section) and a `.grid[hidden]` rule so a collapsed grid actually hides. ## [2.3.0] — 2026-08-27 ### Added - **Jira status durations (chart #17)** — a new "Jira status durations (avg days)" chart on the Active-stats page: average time each Jira workflow status is held, in the board's column order, min-2-tickets. Server endpoint `GET /api/analytics/jira-durations` (PM+) aggregates `jira.statusDurations` (ms) across tickets. - The extension's Jira collector now fetches each issue's **changelog** (`expand=changelog`) and computes **`statusDurations`** (time per status) + **`movements`** (`{at, who, from, to}`), sent inside the `jira` object. - Seed enrichment: `statusDurations` + the Jira **workflow column order** are ingested from the dump's `jira_board_state` (so the chart is populated before any live sync — 73 seeded tickets carry durations). `JiraInfo` type extended with `statusDurations`/`movements`. ## [2.2.0] — 2026-08-27 ### Added - **Jira sync in the extension** (per the sync ADR). One "Sync now" now refreshes **SNOW then Jira**: SNOW stays as the in-page same-origin session collector → `/api/sync`; Jira is pulled by a new service-worker collector via **direct REST** (`/rest/agile/1.0/board/{id}/issue`) authenticated with a **Jira API token** — Basic (email+token, Cloud) or **Bearer PAT** (Server/DC, e.g. `support.dataart.com`). Issue links are built as `/browse/` (custom Jira domains supported). - New extension options: Jira base URL, email, API token/PAT, and board id(s). - New server endpoint **`POST /api/sync/jira`** — **attach-only**: updates only the `jira` JSONB (merged) + `jira_key` on existing tickets, keyed by RITM number; never touches status/state/assignee/activity (avoids the ticket-upsert clobber). Token-authed. Jira RITM↔issue resolved via `customfield_26001` or a `RITM\d+` regex; Jira failures are best-effort and never fail a successful SNOW sync. ### Added - **Polished Excel export** — the board's Excel export is now filterable and readable: the **ticket Number is a clickable ServiceNow hyperlink** (a real OOXML hyperlink relationship — locale-proof, unlike a `HYPERLINK()` formula which breaks in `;`-locale Excel), **auto-filter** on every column, **zebra-striped rows**, taller rows + wider columns, a frozen indigo header, right-aligned numbers, `#,##0` cost, short dates, and a **dated filename** (`forge-active-tickets-YYYY-MM-DD.xlsx`). The standalone Link column was folded into the Number cell. (auto-filter + hyperlinks injected via `fflate`.) - App **favicon** (indigo snowflake) + `theme-color`. - A proper **Excel-brand button icon** (green tile) replacing the placeholder glyph. ### Changed - RITM number links out to ServiceNow in the ticket table + board cards (new tab). - Deploy: ported Husky's `push-to-nas.sh` + on-NAS `deploy.sh` (SSH key pinning, openrsync→tar fallback, Synology PATH/sudo, health poll, `forge-db` volume preserved on `--fresh`); macOS AppleDouble (`._*`) files excluded from sync/build/tests. ### Dependencies - Added `fflate` (xlsx auto-filter post-processing). `write-excel-file` pinned at 2.x (v4 drops the default export + still has no hyperlink support). ## [2.0.0] — 2026-08-27 · **FORGE 2.0** The analytics release. FORGE grows from a ticket board into a full ServiceNow analytics portal ported from the *Let it Snow* Chrome extension, with role-based access, admin tooling, and the complete statistics suite. ### Added - **Full analytics data ingest.** The seed now loads the entire `analytics_data` master (954 ticket records) merged with the operational cache, plus SLA norms, FX rates, size thresholds, brand palette, and the colleague roster. New ticket columns (all additive): `currency_code, opened_by, opened_date, closed_date, to_do_at, in_uat_at, jira_key, ticket_year, size, po_number, invoiced`. A reproducible `dump-to-archives` → `reseed` pipeline regenerates seed data from a storage dump. `app_config` key/value table for norms/thresholds/palettes. - **Server analytics engine** (`server/analytics.ts`, `server/insights.ts`): `/api/analytics/overview`, `/api/analytics/sla`, `/api/insights`, `/api/config`. - **Overall statistics** page — year-overlay grouped bars (opened/closed/revenue, bars↔line + YoY%), a Cost↔Tickets toggle, a ticket-share donut (#/% + top-N), brand→market and requester→brand drill-downs, KPI tiles, lifetime histogram. - **Active statistics** page — by-status, age distribution, opened-per-month, median days-in-status, by-brand. - **PM KPIs — SLA** page — six PM×size heatmaps (TTFR, PM response, avg-close, OTD, assign, preview) scored against per-size norms, green/red vs target. - **PM Insights** page — problem/alert KPIs over the active backlog (unassigned, on-hold, WIP-stalled, customer-replied, awaiting, lifetime, inactive), Jira-breach and client-owed rules, waiting-PO with L1/L2/L3 escalation, revenue-at-risk, and a per-PM roll-up. (Rev-at-risk matches the initial app's own figure within rounding.) - **Board redesign** — 7-state kanban (Unassigned → Open/Assigned → On Hold → WIP → Customer replied → Awaiting) with the WIP→"Customer replied" pseudo-status, SLA subtitles, per-column counts, brand/market/PM filters, board/list toggle, and a ticket detail modal. - **Role-based access control** — four roles (Viewer, PM, Project Leadership, Admin) enforced server-side per route; the client hides nav/pages above a user's role. Only admins can create admins. - **Admin page** — user management (Project Leadership+) and API-token management (Admin only): create/list/revoke `fg_…` sync tokens from the UI. - **Filtered Excel export** — the board's ⭳ Excel button exports the currently filtered tickets as a styled `.xlsx` (branded indigo frozen header, per-column widths, right-aligned numbers, `#,##0` cost, short dates, grid borders). - **Finance ingest** — cost + PO + invoiced per ticket from the finance sheet, enriching active tickets with cost/size and powering waiting-PO. - Config bundle exposed via `/api/config` (brand colours, colleagues, norms, FX). ### Changed - All analytics pages are **full-width** (removed the 1280px cap). - `/api/tokens` is now gated by the **admin session role** instead of an `ADMIN_KEY` header. The `mint-token` CLI is unchanged. - Non-destructive sync upsert: a thin sync no longer nulls seeded-rich fields (`COALESCE`/`CASE` preserve brand/market/description/jira/activity/analytics). ### Fixed - `due_date` timezone off-by-one (emit local `YYYY-MM-DD`, not `toISOString()`). - Activity render-crash vector (ingest sanitizes activity entries; `parseDate` guards). - Seed "active wins" ordering on tickets present in both archives. - Ticket-share donut used a truncated distribution (center 375); now the full 966. ### Security - Read-API authentication (session, username+password, bcrypt, Postgres session store). - Least-privilege database role (`forge_app`, not a superuser) owns the app tables. - API tokens: `fg__`, only the SHA-256 stored, constant-time verify, revocable; `token_hash` never leaked by the list endpoint. - RBAC hardening (post-audit): only an admin can delete an admin; the **last admin** cannot be deleted (lockout guard); deleting a user **purges their sessions**; the session id is **regenerated on login** (anti session-fixation); delete of a missing user returns 404. ## [1.0.0] — 2026-08-27 Initial full-stack scaffold, ported from the *Let it Snow* Chrome extension onto the *Husky repo list* template. ### Added - Vite + React 18 + TypeScript client (SCSS modules, react-router) with a Sidebar/ TopBar layout; Active board, Closed, and basic Statistics pages. - Express + TypeScript server on Postgres (`forge`); self-bootstrapping schema (`initDB`) that seeds bundled ticket archives on first boot. - REST API: `/api/tickets`, `/api/tickets/:number`, `/api/stats`, `/healthz`, and a token-authed `/api/sync` ingest. - Chrome sync extension ("FORGE Snow Sync") — reads active RITM tickets from the ServiceNow Table API and pushes them to the portal with a bearer token. - Multi-stage Dockerfile + docker-compose (bundled Postgres) targeting `forge.mycloud.dp.ua`; README + SETUP docs. [2.0.0]: #200--2026-08-27--forge-20 [1.0.0]: #100--2026-08-27