From 17de3dd72dab707ccad743e90e43039bbaa1948a Mon Sep 17 00:00:00 2001
From: francy51
Date: Wed, 18 Mar 2026 23:40:28 -0400
Subject: [PATCH] Add history window controls and expand taxonomy pack support
- add 3Y/5Y/10Y financial history filtering and reorganize normalization details UI
- add new fiscal taxonomy surface/income bridge/KPI packs and update Rust taxonomy loading
- auto-detect Homebrew SQLite for native `sqlite-vec` in local dev/e2e with docs and env guidance
---
.env.example | 4 +
README.md | 6 +
app/financials/page.tsx | 569 ++++----
app/globals.css | 236 ++--
components/dashboard/metric-card.tsx | 8 +-
components/financials/control-bar.tsx | 35 +-
components/financials/financials-toolbar.tsx | 25 +
.../financials/normalization-summary.tsx | 35 +-
components/financials/statement-matrix.tsx | 496 +++----
.../financials/statement-row-inspector.tsx | 36 +-
components/shell/app-shell.tsx | 42 +-
components/ui/button.tsx | 2 +-
components/ui/input.tsx | 2 +-
components/ui/panel.tsx | 2 +-
components/ui/status-pill.tsx | 19 +-
doc/sqlite-vec-local-setup.md | 48 +
doc/taxonomy-pack-authoring.md | 73 +
docs/architecture/taxonomy.md | 93 +-
lib/financials/history-window.test.ts | 120 ++
lib/financials/history-window.ts | 42 +
lib/financials/statement-view-model.ts | 342 +++--
lib/server/db/index.ts | 100 +-
rust/fiscal-xbrl-core/src/pack_selector.rs | 1175 +++++++++++++----
rust/fiscal-xbrl-core/src/surface_mapper.rs | 126 ++
rust/fiscal-xbrl-core/src/taxonomy_loader.rs | 54 +
rust/fiscal-xbrl-core/src/universal_income.rs | 107 ++
.../fiscal/v1/agriculture.income-bridge.json | 295 +++++
.../fiscal/v1/agriculture.surface.json | 232 ++++
...ontractors_construction.income-bridge.json | 293 ++++
.../v1/contractors_construction.surface.json | 237 ++++
...tors_federal_government.income-bridge.json | 295 +++++
...ontractors_federal_government.surface.json | 197 +++
.../v1/development_stage.income-bridge.json | 274 ++++
.../fiscal/v1/development_stage.surface.json | 118 ++
...ertainment_broadcasters.income-bridge.json | 302 +++++
.../entertainment_broadcasters.surface.json | 216 +++
...inment_cable_television.income-bridge.json | 302 +++++
...ntertainment_cable_television.surface.json | 218 +++
.../entertainment_casinos.income-bridge.json | 304 +++++
.../v1/entertainment_casinos.surface.json | 238 ++++
.../v1/entertainment_films.income-bridge.json | 299 +++++
.../v1/entertainment_films.surface.json | 198 +++
.../v1/entertainment_music.income-bridge.json | 292 ++++
.../v1/entertainment_music.surface.json | 176 +++
.../v1/extractive_mining.income-bridge.json | 300 +++++
.../fiscal/v1/extractive_mining.surface.json | 216 +++
.../fiscal/v1/franchisors.income-bridge.json | 302 +++++
.../fiscal/v1/franchisors.surface.json | 236 ++++
.../fiscal/v1/kpis/agriculture.kpis.json | 5 +
.../kpis/contractors_construction.kpis.json | 5 +
.../contractors_federal_government.kpis.json | 5 +
.../v1/kpis/development_stage.kpis.json | 5 +
.../kpis/entertainment_broadcasters.kpis.json | 5 +
.../entertainment_cable_television.kpis.json | 5 +
.../v1/kpis/entertainment_casinos.kpis.json | 5 +
.../v1/kpis/entertainment_films.kpis.json | 5 +
.../v1/kpis/entertainment_music.kpis.json | 5 +
.../v1/kpis/extractive_mining.kpis.json | 5 +
.../fiscal/v1/kpis/franchisors.kpis.json | 5 +
.../fiscal/v1/kpis/mortgage_banking.kpis.json | 5 +
.../fiscal/v1/kpis/not_for_profit.kpis.json | 5 +
.../v1/kpis/plan_defined_benefit.kpis.json | 5 +
.../kpis/plan_defined_contribution.kpis.json | 5 +
.../v1/kpis/plan_health_welfare.kpis.json | 5 +
.../real_estate_common_interest.kpis.json | 5 +
.../v1/kpis/real_estate_general.kpis.json | 5 +
.../v1/kpis/real_estate_retail_land.kpis.json | 5 +
.../kpis/real_estate_time_sharing.kpis.json | 5 +
.../fiscal/v1/kpis/software.kpis.json | 5 +
.../fiscal/v1/kpis/steamship.kpis.json | 5 +
.../fiscal/v1/kpis/title_plant.kpis.json | 5 +
.../v1/mortgage_banking.income-bridge.json | 300 +++++
.../fiscal/v1/mortgage_banking.surface.json | 235 ++++
.../v1/not_for_profit.income-bridge.json | 274 ++++
.../fiscal/v1/not_for_profit.surface.json | 220 +++
.../plan_defined_benefit.income-bridge.json | 273 ++++
.../v1/plan_defined_benefit.surface.json | 142 ++
...an_defined_contribution.income-bridge.json | 273 ++++
.../v1/plan_defined_contribution.surface.json | 104 ++
.../v1/plan_health_welfare.income-bridge.json | 271 ++++
.../v1/plan_health_welfare.surface.json | 122 ++
..._estate_common_interest.income-bridge.json | 292 ++++
.../real_estate_common_interest.surface.json | 196 +++
.../v1/real_estate_general.income-bridge.json | 300 +++++
.../v1/real_estate_general.surface.json | 236 ++++
...real_estate_retail_land.income-bridge.json | 295 +++++
.../v1/real_estate_retail_land.surface.json | 196 +++
...eal_estate_time_sharing.income-bridge.json | 302 +++++
.../v1/real_estate_time_sharing.surface.json | 256 ++++
.../fiscal/v1/software.income-bridge.json | 305 +++++
rust/taxonomy/fiscal/v1/software.surface.json | 234 ++++
.../fiscal/v1/steamship.income-bridge.json | 302 +++++
.../taxonomy/fiscal/v1/steamship.surface.json | 258 ++++
.../fiscal/v1/title_plant.income-bridge.json | 295 +++++
.../fiscal/v1/title_plant.surface.json | 196 +++
scripts/dev.ts | 169 ++-
scripts/e2e-prepare.ts | 28 +-
scripts/e2e-webserver.ts | 52 +-
scripts/generate-taxonomy.ts | 72 +-
scripts/sqlite-vector-env.test.ts | 146 ++
scripts/sqlite-vector-env.ts | 144 ++
scripts/validate-taxonomy-packs.ts | 354 +++--
102 files changed, 14978 insertions(+), 1316 deletions(-)
create mode 100644 doc/sqlite-vec-local-setup.md
create mode 100644 doc/taxonomy-pack-authoring.md
create mode 100644 lib/financials/history-window.test.ts
create mode 100644 lib/financials/history-window.ts
create mode 100644 rust/taxonomy/fiscal/v1/agriculture.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/agriculture.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/contractors_construction.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/contractors_construction.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/contractors_federal_government.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/contractors_federal_government.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/development_stage.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/development_stage.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_broadcasters.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_broadcasters.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_cable_television.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_cable_television.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_casinos.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_casinos.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_films.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_films.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_music.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/entertainment_music.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/extractive_mining.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/extractive_mining.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/franchisors.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/franchisors.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/agriculture.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/contractors_construction.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/contractors_federal_government.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/development_stage.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/entertainment_broadcasters.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/entertainment_cable_television.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/entertainment_casinos.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/entertainment_films.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/entertainment_music.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/extractive_mining.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/franchisors.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/mortgage_banking.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/not_for_profit.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/plan_defined_benefit.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/plan_defined_contribution.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/plan_health_welfare.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/real_estate_common_interest.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/real_estate_general.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/real_estate_retail_land.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/real_estate_time_sharing.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/software.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/steamship.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/kpis/title_plant.kpis.json
create mode 100644 rust/taxonomy/fiscal/v1/mortgage_banking.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/mortgage_banking.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/not_for_profit.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/not_for_profit.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_defined_benefit.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_defined_benefit.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_defined_contribution.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_defined_contribution.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_health_welfare.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/plan_health_welfare.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_common_interest.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_common_interest.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_general.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_general.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_retail_land.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_retail_land.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_time_sharing.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/real_estate_time_sharing.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/software.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/software.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/steamship.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/steamship.surface.json
create mode 100644 rust/taxonomy/fiscal/v1/title_plant.income-bridge.json
create mode 100644 rust/taxonomy/fiscal/v1/title_plant.surface.json
create mode 100644 scripts/sqlite-vector-env.test.ts
create mode 100644 scripts/sqlite-vector-env.ts
diff --git a/.env.example b/.env.example
index 0d4be1a..a65ba7c 100644
--- a/.env.example
+++ b/.env.example
@@ -8,6 +8,10 @@ APP_PORT=3000
# For app running outside Docker, this resolves to ./data/fiscal.sqlite
# In Docker Compose deployment, default path is /app/data/fiscal.sqlite
DATABASE_URL=file:data/fiscal.sqlite
+# Optional native sqlite-vec setup
+# On macOS local dev/e2e, bun run dev and bun run e2e:webserver auto-detect Homebrew SQLite if present.
+# SQLITE_CUSTOM_LIB_PATH=/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib
+# SQLITE_VEC_EXTENSION_PATH=/absolute/path/to/node_modules/sqlite-vec-darwin-arm64/vec0.dylib
BETTER_AUTH_SECRET=replace-with-a-long-random-secret
BETTER_AUTH_BASE_URL=http://localhost:3000
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000
diff --git a/README.md b/README.md
index eec8ae9..0c2b4f0 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ bun run dev
Open [http://localhost:3000](http://localhost:3000).
`bun run dev` is the local-safe entrypoint. It bootstraps the local SQLite schema from `drizzle/` when needed, forces Better Auth to a localhost origin, uses same-origin API calls, and falls back to local SQLite + Workflow local runtime even if `.env` still contains deployment-oriented values. If port `3000` is already in use and you did not set `PORT`, it automatically picks the next open local port and keeps Better Auth in sync with that port.
+On macOS, `bun run dev` also auto-detects Homebrew SQLite and enables native `sqlite-vec` when `/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib` or `/usr/local/opt/sqlite/lib/libsqlite3.dylib` exists. If no compatible SQLite library is found, the app falls back to table-backed vector storage and search still works. See [doc/sqlite-vec-local-setup.md](doc/sqlite-vec-local-setup.md).
If you need raw `next dev` behavior without those overrides, use:
@@ -80,6 +81,7 @@ bun run test:e2e:ui
```
The Playwright web server boot path uses an isolated SQLite database at `data/e2e.sqlite`, forces local Better Auth origins for the test port, and stores artifacts under `output/playwright/`.
+On macOS, `bun run e2e:webserver` uses the same Homebrew SQLite auto-detection so `sqlite-vec` can load natively when available.
## Docker deployment
@@ -146,6 +148,10 @@ Use root `.env` or root `.env.local`:
# leave blank for same-origin API
NEXT_PUBLIC_API_URL=
DATABASE_URL=file:data/fiscal.sqlite
+# Optional native sqlite-vec setup
+# macOS local dev/e2e auto-detects Homebrew SQLite if present.
+# SQLITE_CUSTOM_LIB_PATH=/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib
+# SQLITE_VEC_EXTENSION_PATH=/absolute/path/to/node_modules/sqlite-vec-darwin-arm64/vec0.dylib
BETTER_AUTH_SECRET=replace-with-a-long-random-secret
BETTER_AUTH_BASE_URL=http://localhost:3000
BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000
diff --git a/app/financials/page.tsx b/app/financials/page.tsx
index 77ba663..0141f17 100644
--- a/app/financials/page.tsx
+++ b/app/financials/page.tsx
@@ -14,7 +14,6 @@ import {
import { format } from "date-fns";
import { useSearchParams } from "next/navigation";
import {
- Bar,
CartesianGrid,
Line,
LineChart,
@@ -25,6 +24,7 @@ import {
} from "recharts";
import {
AlertTriangle,
+ ChevronRight,
ChevronDown,
Download,
RefreshCcw,
@@ -57,6 +57,11 @@ import {
resolveStatementSelection,
type StatementInspectorSelection,
} from "@/lib/financials/statement-view-model";
+import {
+ filterPeriodsByHistoryWindow,
+ financialHistoryLimit,
+ type FinancialHistoryWindow,
+} from "@/lib/financials/history-window";
import { queryKeys } from "@/lib/query/keys";
import { companyFinancialStatementsQueryOptions } from "@/lib/query/options";
import type {
@@ -65,7 +70,6 @@ import type {
DerivedFinancialRow,
FinancialCadence,
FinancialDisplayMode,
- FinancialStatementPeriod,
FinancialSurfaceKind,
FinancialUnit,
RatioRow,
@@ -73,7 +77,6 @@ import type {
StructuredKpiRow,
SurfaceFinancialRow,
TaxonomyStatementRow,
- TrendSeries,
} from "@/lib/types";
type LoadOptions = {
@@ -111,6 +114,15 @@ const CADENCE_OPTIONS = [
{ value: "ltm", label: "LTM" },
];
+const HISTORY_WINDOW_OPTIONS: Array<{
+ value: `${FinancialHistoryWindow}`;
+ label: string;
+}> = [
+ { value: "3", label: "3Y" },
+ { value: "5", label: "5Y" },
+ { value: "10", label: "10Y" },
+];
+
const DISPLAY_MODE_OPTIONS = [
{ value: "standardized", label: "Standard" },
{ value: "faithful", label: "Faithful" },
@@ -410,6 +422,59 @@ function ChartFrame({ children }: { children: React.ReactNode }) {
);
}
+function CollapsibleSection(props: {
+ title: string;
+ subtitle?: string;
+ children: React.ReactNode;
+ defaultOpen?: boolean;
+}) {
+ const [open, setOpen] = useState(props.defaultOpen ?? false);
+
+ return (
+
+
+ {open ? {props.children}
: null}
+
+ );
+}
+
+function SummarySubsection(props: {
+ title: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {props.title}
+
+ {props.children}
+
+ );
+}
+
function FinancialsPageContent() {
const { isPending, isAuthenticated } = useAuthGuard();
const searchParams = useSearchParams();
@@ -423,6 +488,7 @@ function FinancialsPageContent() {
const [cadence, setCadence] = useState("annual");
const [displayMode, setDisplayMode] =
useState("standardized");
+ const [historyWindow, setHistoryWindow] = useState(3);
const [valueScale, setValueScale] = useState("millions");
const [rowSearch, setRowSearch] = useState("");
const [showPercentChange, setShowPercentChange] = useState(false);
@@ -438,10 +504,14 @@ function FinancialsPageContent() {
() => new Set(),
);
const [loading, setLoading] = useState(true);
- const [loadingMore, setLoadingMore] = useState(false);
+ const [, setLoadingMore] = useState(false);
const [syncingFinancials, setSyncingFinancials] = useState(false);
const [error, setError] = useState(null);
const [showTrendChart, setShowTrendChart] = useState(false);
+ const historyFetchLimit = useMemo(
+ () => financialHistoryLimit(cadence, historyWindow),
+ [cadence, historyWindow],
+ );
useEffect(() => {
const fromQuery = searchParams.get("ticker");
@@ -509,7 +579,7 @@ function FinancialsPageContent() {
includeDimensions,
includeFacts: false,
cursor: nextCursor,
- limit: 12,
+ limit: historyFetchLimit,
}),
);
@@ -532,7 +602,14 @@ function FinancialsPageContent() {
setLoadingMore(false);
}
},
- [cadence, queryClient, selectedFlatRowKey, selectedRowRef, surfaceKind],
+ [
+ cadence,
+ historyFetchLimit,
+ queryClient,
+ selectedFlatRowKey,
+ selectedRowRef,
+ surfaceKind,
+ ],
);
const syncFinancials = useCallback(async () => {
@@ -578,6 +655,10 @@ function FinancialsPageContent() {
Date.parse(right.periodEnd ?? right.filingDate),
);
}, [financials?.periods]);
+ const visiblePeriods = useMemo(
+ () => filterPeriodsByHistoryWindow(periods, cadence, historyWindow),
+ [cadence, historyWindow, periods],
+ );
const isTreeStatementMode =
displayMode === "standardized" && isStatementSurfaceKind(surfaceKind);
@@ -708,11 +789,23 @@ function FinancialsPageContent() {
surfaceKind === "income_statement" ||
surfaceKind === "cash_flow_statement"
) {
- return standardizedRows.find((row) => row.key === "revenue") ?? null;
+ return (
+ standardizedRows.find((row) => row.key === "revenue") ??
+ standardizedRows.find((row) => row.key === "net_cash_from_operating") ??
+ standardizedRows.find((row) => row.key === "operating_income") ??
+ null
+ );
}
if (surfaceKind === "balance_sheet") {
- return standardizedRows.find((row) => row.key === "total_assets") ?? null;
+ return (
+ standardizedRows.find((row) => row.key === "total_assets") ??
+ standardizedRows.find(
+ (row) => row.key === "total_liabilities_and_equity",
+ ) ??
+ standardizedRows.find((row) => row.key === "stockholders_equity") ??
+ null
+ );
}
return null;
@@ -724,7 +817,7 @@ function FinancialsPageContent() {
const trendSeries = financials?.trendSeries ?? [];
const chartData = useMemo(() => {
- return periods.map((period) => ({
+ return visiblePeriods.map((period) => ({
label: formatLongDate(period.periodEnd ?? period.filingDate),
...Object.fromEntries(
trendSeries.map((series) => [
@@ -733,7 +826,7 @@ function FinancialsPageContent() {
]),
),
}));
- }, [periods, trendSeries]);
+ }, [trendSeries, visiblePeriods]);
const controlSections = useMemo(() => {
const sections: FinancialControlSection[] = [
@@ -761,6 +854,16 @@ function FinancialsPageContent() {
setExpandedRowKeys(new Set());
},
},
+ {
+ key: "history",
+ label: "History",
+ options: HISTORY_WINDOW_OPTIONS,
+ value: String(historyWindow),
+ onChange: (value) =>
+ setHistoryWindow(
+ Number.parseInt(value, 10) as FinancialHistoryWindow,
+ ),
+ },
];
if (
@@ -791,7 +894,7 @@ function FinancialsPageContent() {
});
return sections;
- }, [cadence, displayMode, surfaceKind, valueScale]);
+ }, [cadence, displayMode, historyWindow, surfaceKind, valueScale]);
const toggleExpandedRow = useCallback((key: string) => {
setExpandedRowKeys((current) => {
@@ -1069,7 +1172,7 @@ function FinancialsPageContent() {
edits or derived rows are available yet.
- ) : periods.length === 0 ||
+ ) : visiblePeriods.length === 0 ||
(isTreeStatementMode
? (treeModel?.visibleNodeCount ?? 0) === 0
: filteredRows.length === 0) ? (
@@ -1081,7 +1184,7 @@ function FinancialsPageContent() {
{isStatementSurfaceKind(surfaceKind) ? (
- USD · {valueScaleLabel}
+ USD · {valueScaleLabel} · Latest {historyWindow} years
{isTreeStatementMode && hasUnmappedResidualRows ? (
@@ -1096,7 +1199,7 @@ function FinancialsPageContent() {
) : null}
{isTreeStatementMode && treeModel ? (
50}
/>
) : (
-
-
+
+
- |
- Metric
- |
- {periods.map((period) => (
+ Metric |
+ {visiblePeriods.map((period) => (
@@ -1136,7 +1236,7 @@ function FinancialsPageContent() {
{group.label ? (
|
{group.label}
@@ -1146,14 +1246,9 @@ function FinancialsPageContent() {
{group.rows.map((row) => (
| setSelectedFlatRowKey(row.key)}
>
- |
+ |
|
- {periods.map((period, index) => (
+ {visiblePeriods.map((period, index) => (
{buildDisplayValue({
row,
periodId: period.id,
previousPeriodId:
index > 0
- ? (periods[index - 1]?.id ?? null)
+ ? (visiblePeriods[index - 1]?.id ?? null)
: null,
commonSizeRow,
displayMode,
@@ -1210,207 +1305,235 @@ function FinancialsPageContent() {
)}
- {isTreeStatementMode && isStatementSurfaceKind(surfaceKind) ? (
-
- ) : (
-
- {!selectedRow ? (
-
- Select a row to inspect details.
-
- ) : (
-
-
-
- Label
-
- {selectedRow.label}
-
-
-
- Key
-
- {selectedRow.key}
-
-
-
-
- {isTaxonomyRow(selectedRow) ? (
-
-
- Taxonomy Concept
-
-
- {selectedRow.qname}
-
-
+ {financials ? (
+
+
+
+ {isTreeStatementMode && isStatementSurfaceKind(surfaceKind) ? (
+
) : (
-
-
-
- Category
+
+ {!selectedRow ? (
+
+ Select a row to inspect details.
-
- {selectedRow.category}
-
-
-
- Unit
-
- {selectedRow.unit}
-
-
-
+ ) : (
+
+
+
+
+ Label
+
+
+ {selectedRow.label}
+
+
+
+
+ Key
+
+
+ {selectedRow.key}
+
+
+
+
+ {isTaxonomyRow(selectedRow) ? (
+
+
+ Taxonomy Concept
+
+
+ {selectedRow.qname}
+
+
+ ) : (
+
+
+
+ Category
+
+
+ {selectedRow.category}
+
+
+
+
+ Unit
+
+
+ {selectedRow.unit}
+
+
+
+ )}
+
+ {isDerivedRow(selectedRow) ? (
+
+
+ Source Row Keys
+
+
+ {(selectedRow.sourceRowKeys ?? []).join(", ") ||
+ "n/a"}
+
+
+ Source Concepts
+
+
+ {(selectedRow.sourceConcepts ?? []).join(", ") ||
+ "n/a"}
+
+
+ Source Fact IDs
+
+
+ {(selectedRow.sourceFactIds ?? []).join(", ") ||
+ "n/a"}
+
+
+ ) : null}
+
+ {!selectedRow ||
+ !("hasDimensions" in selectedRow) ||
+ !selectedRow.hasDimensions ? (
+
+ No dimensional drill-down is available for this row.
+
+ ) : dimensionRows.length === 0 ? (
+
+ No dimensional facts were returned for the selected
+ row.
+
+ ) : (
+
+
+
+
+ | Period |
+ Axis |
+ Member |
+ Value |
+
+
+
+ {dimensionRows.map((row, index) => (
+
+ |
+ {visiblePeriods.find(
+ (period) => period.id === row.periodId,
+ )?.periodLabel ?? row.periodId}
+ |
+ {row.axis} |
+ {row.member} |
+
+ {formatMetricValue({
+ value: row.value,
+ unit: isTaxonomyRow(selectedRow)
+ ? "currency"
+ : selectedRow.unit,
+ scale: valueScale,
+ rowKey: selectedRow.key,
+ surfaceKind,
+ })}
+ |
+
+ ))}
+
+
+
+ )}
+
+ )}
+
)}
+
- {isDerivedRow(selectedRow) ? (
-
-
- Source Row Keys
-
-
- {(selectedRow.sourceRowKeys ?? []).join(", ") || "n/a"}
-
-
- Source Concepts
-
-
- {(selectedRow.sourceConcepts ?? []).join(", ") || "n/a"}
-
-
- Source Fact IDs
-
-
- {(selectedRow.sourceFactIds ?? []).join(", ") || "n/a"}
-
+ {(surfaceKind === "income_statement" ||
+ surfaceKind === "balance_sheet" ||
+ surfaceKind === "cash_flow_statement") && (
+
+
+
+
+ Overall status:{" "}
+ {financials.metrics.validation?.status ?? "not_run"}
+
- ) : null}
-
- {!selectedRow ||
- !("hasDimensions" in selectedRow) ||
- !selectedRow.hasDimensions ? (
-
- No dimensional drill-down is available for this row.
-
- ) : dimensionRows.length === 0 ? (
-
- No dimensional facts were returned for the selected row.
-
- ) : (
-
-
-
-
- | Period |
- Axis |
- Member |
- Value |
-
-
-
- {dimensionRows.map((row, index) => (
-
- |
- {periods.find(
- (period) => period.id === row.periodId,
- )?.periodLabel ?? row.periodId}
- |
- {row.axis} |
- {row.member} |
-
- {formatMetricValue({
- value: row.value,
- unit: isTaxonomyRow(selectedRow)
- ? "currency"
- : selectedRow.unit,
- scale: valueScale,
- rowKey: selectedRow.key,
- surfaceKind,
- })}
- |
+ {(financials.metrics.validation?.checks.length ?? 0) === 0 ? (
+
+ No validation checks available yet.
+
+ ) : (
+
+
+
+
+ | Metric |
+ Taxonomy |
+ LLM (PDF) |
+ Status |
+ Pages |
- ))}
-
-
-
- )}
-
- )}
-
- )}
+
+
+ {financials.metrics.validation?.checks.map((check) => (
+
+ | {check.metricKey} |
+
+ {formatMetricValue({
+ value: check.taxonomyValue,
+ unit: "currency",
+ scale: valueScale,
+ rowKey: check.metricKey,
+ surfaceKind,
+ })}
+ |
+
+ {formatMetricValue({
+ value: check.llmValue,
+ unit: "currency",
+ scale: valueScale,
+ rowKey: check.metricKey,
+ surfaceKind,
+ })}
+ |
+ {check.status} |
+
+ {(check.evidencePages ?? []).join(", ") || "n/a"}
+ |
+
+ ))}
+
+
+
+ )}
+
+ )}
- {(surfaceKind === "income_statement" ||
- surfaceKind === "balance_sheet" ||
- surfaceKind === "cash_flow_statement") &&
- financials ? (
-
-
-
-
- Overall status:{" "}
- {financials.metrics.validation?.status ?? "not_run"}
-
+ {isTreeStatementMode ? (
+
+
+
+ ) : null}
- {(financials.metrics.validation?.checks.length ?? 0) === 0 ? (
-
- No validation checks available yet.
-
- ) : (
-
-
-
-
- | Metric |
- Taxonomy |
- LLM (PDF) |
- Status |
- Pages |
-
-
-
- {financials.metrics.validation?.checks.map((check) => (
-
- | {check.metricKey} |
-
- {formatMetricValue({
- value: check.taxonomyValue,
- unit: "currency",
- scale: valueScale,
- rowKey: check.metricKey,
- surfaceKind,
- })}
- |
-
- {formatMetricValue({
- value: check.llmValue,
- unit: "currency",
- scale: valueScale,
- rowKey: check.metricKey,
- surfaceKind,
- })}
- |
- {check.status} |
- {(check.evidencePages ?? []).join(", ") || "n/a"} |
-
- ))}
-
-
-
- )}
-
- ) : null}
-
- {financials && isTreeStatementMode ? (
-
+
) : null}
);
diff --git a/app/globals.css b/app/globals.css
index 7ba5479..96c00e1 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,25 +1,23 @@
@import "tailwindcss";
:root {
- --font-display:
- "Avenir Next", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
- --font-mono:
- "Menlo", "SFMono-Regular", "Consolas", "Liberation Mono", monospace;
- --bg-0: #121417;
- --bg-1: #181b20;
- --bg-2: #21252b;
- --panel: rgba(28, 31, 36, 0.84);
- --panel-soft: rgba(36, 39, 45, 0.72);
- --panel-bright: rgba(49, 53, 60, 0.94);
- --line-weak: rgba(196, 202, 211, 0.18);
- --line-strong: rgba(220, 226, 234, 0.34);
- --accent: #d9dee5;
- --accent-strong: #f4f7fb;
- --danger: #ff8e8e;
- --danger-soft: rgba(111, 46, 46, 0.42);
- --terminal-bright: #f3f5f7;
- --terminal-muted: #a1a9b3;
- --focus-ring: rgba(229, 231, 235, 0.14);
+ --font-display: system-ui, -apple-system, sans-serif;
+ --font-mono: "SF Mono", "Fira Code", monospace;
+ --bg-0: #0f0f0f;
+ --bg-1: #161616;
+ --bg-2: #1c1c1c;
+ --panel: #1a1a1a;
+ --panel-soft: #222222;
+ --panel-bright: #2a2a2a;
+ --line-weak: rgba(255, 255, 255, 0.08);
+ --line-strong: rgba(255, 255, 255, 0.16);
+ --accent: #e0e0e0;
+ --accent-strong: #ffffff;
+ --danger: #ff6b6b;
+ --danger-soft: rgba(255, 107, 107, 0.15);
+ --terminal-bright: #f0f0f0;
+ --terminal-muted: #888888;
+ --focus-ring: rgba(255, 255, 255, 0.1);
}
* {
@@ -58,18 +56,7 @@ body {
overflow-x: hidden;
font-family: var(--font-display), sans-serif;
color: var(--terminal-bright);
- background:
- radial-gradient(
- circle at 18% -10%,
- rgba(170, 178, 188, 0.16),
- transparent 35%
- ),
- radial-gradient(
- circle at 84% 0%,
- rgba(121, 128, 138, 0.14),
- transparent 30%
- ),
- linear-gradient(140deg, var(--bg-0), var(--bg-1) 50%, var(--bg-2));
+ background: var(--bg-0);
}
.app-surface,
@@ -79,36 +66,12 @@ body {
overflow: hidden;
}
-.ambient-grid {
- position: absolute;
- inset: 0;
- background-image:
- linear-gradient(rgba(204, 210, 218, 0.06) 1px, transparent 1px),
- linear-gradient(90deg, rgba(204, 210, 218, 0.05) 1px, transparent 1px);
- background-size: 34px 34px;
- mask-image: radial-gradient(ellipse at center, black 20%, transparent 75%);
- pointer-events: none;
-}
-
-.noise-layer {
- position: absolute;
- inset: 0;
- pointer-events: none;
- opacity: 0.24;
- background-image: radial-gradient(
- rgba(220, 226, 234, 0.1) 0.7px,
- transparent 0.7px
- );
- background-size: 4px 4px;
-}
-
.terminal-caption {
font-family: var(--font-mono), monospace;
}
.panel-heading {
font-family: var(--font-mono), monospace;
- letter-spacing: 0.08em;
}
a,
@@ -126,23 +89,15 @@ textarea {
.data-surface {
border: 1px solid var(--line-weak);
- border-radius: 1rem;
- background: linear-gradient(
- 180deg,
- rgba(40, 43, 49, 0.92),
- rgba(24, 27, 32, 0.78)
- );
+ border-radius: 0.5rem;
+ background: var(--panel);
}
.data-table-wrap {
overflow-x: auto;
border: 1px solid var(--line-weak);
- border-radius: 1rem;
- background: linear-gradient(
- 180deg,
- rgba(34, 37, 42, 0.9),
- rgba(20, 23, 27, 0.76)
- );
+ border-radius: 0.5rem;
+ background: var(--panel);
}
.data-table th,
@@ -163,54 +118,31 @@ textarea {
}
.data-table tbody tr:hover {
- background-color: rgba(63, 68, 76, 0.32);
-}
-
-@media (prefers-reduced-motion: no-preference) {
- .ambient-grid {
- animation: subtle-grid-shift 18s linear infinite;
- }
-
- @keyframes subtle-grid-shift {
- 0% {
- transform: translateY(0px);
- }
- 50% {
- transform: translateY(-8px);
- }
- 100% {
- transform: translateY(0px);
- }
- }
-}
-
-@media (max-width: 1024px) {
- .ambient-grid {
- background-size: 26px 26px;
- }
+ background-color: rgba(255, 255, 255, 0.04);
}
@media (max-width: 640px) {
- body {
- background:
- radial-gradient(
- circle at 24% -4%,
- rgba(170, 178, 188, 0.14),
- transparent 36%
- ),
- radial-gradient(
- circle at 82% 2%,
- rgba(121, 128, 138, 0.12),
- transparent 30%
- ),
- linear-gradient(155deg, var(--bg-0), var(--bg-1) 54%, var(--bg-2));
- }
-
.data-table th,
.data-table td {
padding: 0.65rem 0.55rem;
font-size: 0.8125rem;
}
+
+ .financial-matrix-metric-col {
+ width: 18rem;
+ }
+
+ .financial-matrix-period-col {
+ width: 8rem;
+ }
+
+ .financial-matrix-header,
+ .financial-matrix-section-cell,
+ .financial-matrix-sticky-cell,
+ .financial-matrix-value-cell {
+ padding-left: 0.65rem;
+ padding-right: 0.65rem;
+ }
}
.panel-compact {
@@ -244,7 +176,87 @@ textarea {
}
.data-table-dense tbody tr:hover {
- background-color: rgba(63, 68, 76, 0.32);
+ background-color: rgba(255, 255, 255, 0.04);
+}
+
+.financials-table tbody tr:hover {
+ background-color: transparent;
+}
+
+.financial-matrix-wrap {
+ overflow: auto;
+ border: 1px solid var(--line-weak);
+ border-radius: 0.75rem;
+ background: var(--panel);
+}
+
+.financial-matrix {
+ width: max-content;
+ min-width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ table-layout: fixed;
+}
+
+.financial-matrix-metric-col {
+ width: 24rem;
+}
+
+.financial-matrix-period-col {
+ width: 9rem;
+}
+
+.financial-matrix-header {
+ position: sticky;
+ top: 0;
+ z-index: 10;
+ padding: 0.85rem 0.9rem;
+ border-bottom: 1px solid var(--line-strong);
+ background: color-mix(in srgb, var(--panel) 92%, black 8%);
+ text-align: right;
+ white-space: nowrap;
+ font-family: var(--font-mono), monospace;
+}
+
+.financial-matrix-section-row td {
+ border-bottom: 1px solid var(--line-strong);
+}
+
+.financial-matrix-section-cell {
+ padding: 0.7rem 0.9rem;
+ background: rgba(255, 255, 255, 0.04);
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--terminal-muted);
+}
+
+.financial-matrix-row-detail td {
+ background: rgba(255, 255, 255, 0.015);
+}
+
+.financial-matrix-sticky-cell,
+.financial-matrix-value-cell {
+ padding: 0.45rem 0.9rem;
+ border-bottom: 1px solid var(--line-weak);
+ white-space: nowrap;
+ vertical-align: top;
+}
+
+.financial-matrix-sticky-cell {
+ border-right: 1px solid var(--line-weak);
+ background: var(--panel);
+}
+
+.financial-matrix-sticky-cell-detail {
+ background: color-mix(in srgb, var(--panel) 92%, white 8%);
+}
+
+.financial-matrix-value-cell {
+ text-align: right;
+ font-variant-numeric: tabular-nums;
+ color: var(--terminal-bright);
}
.metric-compact {
@@ -382,12 +394,8 @@ textarea {
.screener-table-wrap {
overflow-x: auto;
border: 1px solid var(--line-weak);
- border-radius: 0.75rem;
- background: linear-gradient(
- 180deg,
- rgba(34, 37, 42, 0.9),
- rgba(20, 23, 27, 0.76)
- );
+ border-radius: 0.5rem;
+ background: var(--panel);
}
.screener-table-wrap thead {
diff --git a/components/dashboard/metric-card.tsx b/components/dashboard/metric-card.tsx
index 220a475..afa1eec 100644
--- a/components/dashboard/metric-card.tsx
+++ b/components/dashboard/metric-card.tsx
@@ -41,7 +41,7 @@ export function MetricCard({
className,
)}
>
-
+
{label}
@@ -68,10 +68,8 @@ export function MetricCard({
className,
)}
>
-
- {label}
-
-
+ {label}
+
{value}
{delta ? (
diff --git a/components/financials/control-bar.tsx b/components/financials/control-bar.tsx
index b5cbbd0..f6a309d 100644
--- a/components/financials/control-bar.tsx
+++ b/components/financials/control-bar.tsx
@@ -1,7 +1,7 @@
-import { Button } from '@/components/ui/button';
-import { cn } from '@/lib/utils';
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
-type ControlButtonVariant = 'primary' | 'ghost' | 'secondary' | 'danger';
+type ControlButtonVariant = "primary" | "ghost" | "secondary" | "danger";
export type FinancialControlOption = {
value: string;
@@ -34,19 +34,25 @@ type FinancialControlBarProps = {
};
export function FinancialControlBar({
- title = 'Control Bar',
+ title = "Control Bar",
subtitle,
sections,
actions,
- className
+ className,
}: FinancialControlBarProps) {
return (
-
+
- {title}
+
+ {title}
+
{subtitle ? (
- {subtitle}
+
+ {subtitle}
+
) : null}
@@ -56,7 +62,7 @@ export function FinancialControlBar({
| |