From 2c87ab499a5154e697fd1c1f4824d2d66c9f00cd Mon Sep 17 00:00:00 2001
From: Jonathan Kirkwood
Date: Fri, 3 Jul 2026 09:23:47 -0500
Subject: [PATCH] 0.2.35: exited positions show history clipped at the exit
date
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The eNAV keeps producing statements after an exit (quarterly for funds,
event-driven for SPVs), so an unfiltered graph would crash to zero.
Now the exited card gets its collapsible History back — chart + table
showing the capital journey up to exited_on only. Post-exit statements
stay recorded (audit trail; Undo restores full history) but are never
plotted. History section extracted into a shared HistorySection
component; toggle counts "statements" not "quarters" (SPV statements
are event-driven).
Co-Authored-By: Claude Opus 4.8
---
deploy/package.json | 2 +-
deploy/startos/install/versions/index.ts | 5 +-
deploy/startos/install/versions/v_0_2_35.ts | 13 ++++
frontend/public/sw.js | 2 +-
frontend/src/portal/InvestorPortalView.tsx | 67 ++++++++++++---------
frontend/src/version.ts | 2 +-
6 files changed, 59 insertions(+), 32 deletions(-)
create mode 100644 deploy/startos/install/versions/v_0_2_35.ts
diff --git a/deploy/package.json b/deploy/package.json
index a937ea2..6f9084d 100644
--- a/deploy/package.json
+++ b/deploy/package.json
@@ -1,6 +1,6 @@
{
"name": "ten31portal-startos",
- "version": "0.2.34",
+ "version": "0.2.35",
"private": true,
"scripts": {
"build": "npm run check && rm -rf ./javascript && ncc build startos/index.ts -o ./javascript",
diff --git a/deploy/startos/install/versions/index.ts b/deploy/startos/install/versions/index.ts
index c96d86b..a55a7e8 100644
--- a/deploy/startos/install/versions/index.ts
+++ b/deploy/startos/install/versions/index.ts
@@ -1,4 +1,4 @@
-export { v_0_2_34 as current } from './v_0_2_34'
+export { v_0_2_35 as current } from './v_0_2_35'
import { v_0_1_0 } from './v_0_1_0'
import { v_0_2_0 } from './v_0_2_0'
import { v_0_2_1 } from './v_0_2_1'
@@ -33,4 +33,5 @@ import { v_0_2_30 } from './v_0_2_30'
import { v_0_2_31 } from './v_0_2_31'
import { v_0_2_32 } from './v_0_2_32'
import { v_0_2_33 } from './v_0_2_33'
-export const other = [v_0_1_0, v_0_2_0, v_0_2_1, v_0_2_3, v_0_2_4, v_0_2_5, v_0_2_6, v_0_2_7, v_0_2_8, v_0_2_9, v_0_2_10, v_0_2_11, v_0_2_12, v_0_2_13, v_0_2_14, v_0_2_15, v_0_2_16, v_0_2_17, v_0_2_18, v_0_2_19, v_0_2_20, v_0_2_21, v_0_2_22, v_0_2_23, v_0_2_24, v_0_2_25, v_0_2_26, v_0_2_27, v_0_2_28, v_0_2_29, v_0_2_30, v_0_2_31, v_0_2_32, v_0_2_33]
+import { v_0_2_34 } from './v_0_2_34'
+export const other = [v_0_1_0, v_0_2_0, v_0_2_1, v_0_2_3, v_0_2_4, v_0_2_5, v_0_2_6, v_0_2_7, v_0_2_8, v_0_2_9, v_0_2_10, v_0_2_11, v_0_2_12, v_0_2_13, v_0_2_14, v_0_2_15, v_0_2_16, v_0_2_17, v_0_2_18, v_0_2_19, v_0_2_20, v_0_2_21, v_0_2_22, v_0_2_23, v_0_2_24, v_0_2_25, v_0_2_26, v_0_2_27, v_0_2_28, v_0_2_29, v_0_2_30, v_0_2_31, v_0_2_32, v_0_2_33, v_0_2_34]
diff --git a/deploy/startos/install/versions/v_0_2_35.ts b/deploy/startos/install/versions/v_0_2_35.ts
new file mode 100644
index 0000000..de1c74b
--- /dev/null
+++ b/deploy/startos/install/versions/v_0_2_35.ts
@@ -0,0 +1,13 @@
+import { VersionInfo } from '@start9labs/start-sdk'
+
+export const v_0_2_35 = VersionInfo.of({
+ version: '0.2.35:0',
+ releaseNotes: {
+ en_US:
+ "Exited positions now show the member's capital history up to the exit date: the collapsible History chart/table returns on the exited card, clipped at the exit — statements the eNAV keeps producing after the exit are recorded but never plotted, so the line ends at the exit instead of crashing to zero. History toggle now counts 'statements' (SPVs have event-driven statements, funds quarterly).",
+ },
+ migrations: {
+ up: async ({ effects }) => {},
+ down: async ({ effects }) => {},
+ },
+})
diff --git a/frontend/public/sw.js b/frontend/public/sw.js
index 5426b74..51d5e5f 100644
--- a/frontend/public/sw.js
+++ b/frontend/public/sw.js
@@ -3,7 +3,7 @@
// - content-hashed /assets/* are cache-first (immutable, safe forever)
// - /api/* is never cached
// Bump CACHE on each release so old entries are purged.
-const CACHE = 'ten31-portal-0.2.34'
+const CACHE = 'ten31-portal-0.2.35'
self.addEventListener('install', () => self.skipWaiting())
diff --git a/frontend/src/portal/InvestorPortalView.tsx b/frontend/src/portal/InvestorPortalView.tsx
index 8f1d94f..c56e8b5 100644
--- a/frontend/src/portal/InvestorPortalView.tsx
+++ b/frontend/src/portal/InvestorPortalView.tsx
@@ -256,18 +256,13 @@ function CapitalBlock({
() => [...accounts].sort((a, b) => a.as_of_date.localeCompare(b.as_of_date)),
[accounts],
);
- const chartPoints: CapitalPoint[] = history.map((a) => ({
- date: a.as_of_date,
- value: a.ending_balance_cents,
- paidIn: a.contributions_cents,
- distributions: a.distributions_cents,
- }));
- // Collapsed by default so the portal opens clean; the investor expands the trend per fund.
- const [showChart, setShowChart] = useState(false);
// A sold/transferred stake: the fund's books show $0 with no distribution, which is not a
- // loss. Show a quiet Exited badge and keep the documents; no balance, no gain/loss.
+ // loss. Show a quiet Exited badge and their capital journey UP TO the exit — statements
+ // the eNAV keeps producing after that date are recorded but never plotted, so the line
+ // ends at the exit instead of crashing to zero.
if (latest.exited_on) {
+ const clipped = history.filter((a) => a.as_of_date <= latest.exited_on!);
return (
{label &&
{label}
}
@@ -279,6 +274,7 @@ function CapitalBlock({
This position was sold or transferred. Documents remain available below.
+
);
}
@@ -314,22 +310,41 @@ function CapitalBlock({
)}
- {history.length > 1 && (
-
-
- {showChart && (
-
+
+
+ );
+}
+
+// Collapsible capital-over-time chart + statement table. Collapsed by default so the portal
+// opens clean; the investor expands the trend per fund. SPVs get sparse event-driven points,
+// funds quarterly ones — the chart plots whatever statements exist.
+function HistorySection({ history }: { history: CapitalAccount[] }) {
+ const [showChart, setShowChart] = useState(false);
+ if (history.length < 2) return null;
+
+ const chartPoints: CapitalPoint[] = history.map((a) => ({
+ date: a.as_of_date,
+ value: a.ending_balance_cents,
+ paidIn: a.contributions_cents,
+ distributions: a.distributions_cents,
+ }));
+
+ return (
+
+
+ {showChart && (
+
@@ -351,8 +366,6 @@ function CapitalBlock({
))}
-
- )}
)}
diff --git a/frontend/src/version.ts b/frontend/src/version.ts
index 7a68327..e37b1fe 100644
--- a/frontend/src/version.ts
+++ b/frontend/src/version.ts
@@ -1,4 +1,4 @@
// Bumped each release so the running build is visible in the UI.
// If the number shown in the app doesn't match the installed s9pk version,
// the new frontend isn't actually being served.
-export const APP_VERSION = "0.2.34";
+export const APP_VERSION = "0.2.35";