Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,30 @@ popups gain per-leg deleverage attribution; the conflict chips become target sel
No change to single-strategy mode, to D-016/D-017 attribution, or to the sensitivity
grid.

### D-029 — Public anonymization of manager/fund names + hidden reveal toggle
**Date:** 2026-07-02
**Decision (owner):** For the general-public build, the specific manager brand
**"Quantinno"** and its fund **"QFAF" / "Quantinno Fundamental Arbitrage Fund"** (D-007)
are replaced with generic labels. Anonymized is the **DEFAULT**; the owner can reveal the
real names for internal use via a hidden trigger.
**Mapping (anonymized):** `Quantinno` → **Manager**, `QFAF` → **Fund**, full fund name →
**Fundamental Arbitrage Fund** (the "Quantinno" prefix and any `(QFAF)` gloss are dropped).
The bare noun forms are intentional so a preceding article flows through naturally ("the
QFAF" → "the Fund"; "Quantinno alpha" → "Manager alpha") while standalone labels stay
clean ("QFAF Value" → "Fund Value", not "the Fund Value").
**Hidden toggle:** **five rapid clicks on the workspace brand** (`.wx-brand`) flip real
names on/off, with a brief "Internal names on / Anonymized" confirmation. No cursor,
tooltip, or ARIA affordance — undiscoverable to public users.
**Status: IMPLEMENTED (2026-07-02).** New `src/branding.ts` holds the transform +
a global `localStorage`-backed reveal store (`useSyncExternalStore`, key
`taxCalc:reveal-brand`, default off) so every subscribed component updates live — no page
reload, in-progress inputs preserved. All user-visible strings route through `brandText()`;
the 55 popup strings flow through the single `getPopupContent()` chokepoint. Only display
strings changed — data-model field names, CSS classes, and CSV/Excel serialization keys
keep the literal `qfaf`/`quantinno` spelling, so no CalculatorInputs field was added and
CSV/Excel round-trips are unaffected. **Standing requirement:** new user-visible
manager/fund text must be wrapped in `brandText()` (or come from `getPopupContent`).

---

## Pending decision queue (next batches)
Expand Down
23 changes: 13 additions & 10 deletions src/AdvancedMode/QfafTestByYear.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
HIST_ORD_LOSS_AVG,
} from '../qfafTestData';
import './QfafTestByYear.css';
import { brandText, useBrandRevealed } from '../branding';

interface QfafTestByYearProps {
filingStatus: FilingStatus;
Expand Down Expand Up @@ -144,6 +145,7 @@ const ROW_DEFINITIONS: RowDef[] = [
];

export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {
useBrandRevealed();
const [state, dispatch] = useReducer(reducer, null, () => ({
assumptions: { ...DEFAULT_ASSUMPTIONS },
}));
Expand Down Expand Up @@ -201,7 +203,7 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {
<h3>Assumptions: Adjust the cells in orange</h3>
<div className="assumptions-grid">
<div className="assumption-row">
<label>Initial QFAF Investment</label>
<label>{brandText('Initial QFAF Investment')}</label>
<div className="input-with-prefix editable-cell">
<span className="prefix">$</span>
<input
Expand Down Expand Up @@ -290,7 +292,8 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {

<div className="assumption-row assumption-row-wide">
<label>
QFAF Generation Rate: {(state.assumptions.qfafGenerationRate * 100).toFixed(0)}%
{brandText('QFAF Generation Rate')}:{' '}
{(state.assumptions.qfafGenerationRate * 100).toFixed(0)}%
</label>
<div className="generation-rate-slider">
<input
Expand Down Expand Up @@ -349,7 +352,7 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {
<td className="row-label">
{rowDef.key === 'dealsCollateralValue'
? `Deals Collateral Value (${getStrategyLabel(state.assumptions.coreStrategyId)})`
: rowDef.label}
: brandText(rowDef.label)}
</td>
{results.map(result => (
<td key={result.year} className="cell-value">
Expand All @@ -370,7 +373,7 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {

{/* Historical Performance Section */}
<div className="historical-performance-section">
<h3>Historical QFAF Performance</h3>
<h3>{brandText('Historical QFAF Performance')}</h3>

<div className="historical-tables-grid">
{/* Annual Returns + Breakdown */}
Expand Down Expand Up @@ -482,16 +485,16 @@ export function QfafTestByYear({ filingStatus }: QfafTestByYearProps) {
<h4>Calculation Notes</h4>
<ul>
<li>
<strong>QFAF Subscription:</strong> Sized based on initial investment with Year 1
adjustment factor (1.049×), then decays ~7.7% annually.
<strong>{brandText('QFAF Subscription:')}</strong> Sized based on initial investment
with Year 1 adjustment factor (1.049×), then decays ~7.7% annually.
</li>
<li>
<strong>Deals Collateral:</strong> Calculated so total ST losses = QFAF ST gains.
Combines Overlay (growing at {(QUANTINNO_ALPHA_RATE * 100).toFixed(2)}%) + Core
collateral.
<strong>Deals Collateral:</strong> Calculated so{' '}
{brandText('total ST losses = QFAF ST gains')}. Combines Overlay (growing at{' '}
{(QUANTINNO_ALPHA_RATE * 100).toFixed(2)}%) + Core collateral.
</li>
<li>
<strong>Ordinary Losses:</strong> QFAF generates{' '}
<strong>Ordinary Losses:</strong> {brandText('QFAF generates')}{' '}
{(state.assumptions.qfafGenerationRate * 100).toFixed(0)}% of subscription as ordinary
losses.
</li>
Expand Down
10 changes: 7 additions & 3 deletions src/AdvancedMode/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
formatPercent as formatPercentBase,
} from '../utils/formatters';
import './SettingsPanel.css';
import { brandText, useBrandRevealed } from '../branding';

interface SettingsPanelProps {
settings: AdvancedSettings;
Expand All @@ -17,6 +18,7 @@ interface SettingsPanelProps {
const formatPercent = (value: number) => formatPercentBase(value, 1);

export function SettingsPanel({ settings, onChange, onReset }: SettingsPanelProps) {
useBrandRevealed();
const handleChange = (path: string, value: number | boolean) => {
const keys = path.split('.');
if (keys.length === 1) {
Expand Down Expand Up @@ -70,14 +72,16 @@ export function SettingsPanel({ settings, onChange, onReset }: SettingsPanelProp
<div className="settings-grid">
{/* QFAF Mechanics */}
<div className="settings-section">
<h4>QFAF Mechanics</h4>
<h4>{brandText('QFAF Mechanics')}</h4>

<div className="setting-row">
<div className="setting-label">
<span className="setting-name">
<InfoText contentKey="setting-qfaf-growth">QFAF Growth</InfoText>
<InfoText contentKey="setting-qfaf-growth">{brandText('QFAF Growth')}</InfoText>
</span>
<span className="setting-hint">
{brandText('Whether QFAF appreciates with market returns')}
</span>
<span className="setting-hint">Whether QFAF appreciates with market returns</span>
</div>
<label className={`toggle-switch ${!settings.qfafGrowthEnabled ? 'modified' : ''}`}>
<input
Expand Down
8 changes: 5 additions & 3 deletions src/AdvancedMode/StrategyComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CalculatorInputs, ComparisonResult } from '../types';
import { InfoText } from '../InfoPopup';
import { formatCurrency, formatPercent } from '../utils/formatters';
import './StrategyComparison.css';
import { brandText, useBrandRevealed } from '../branding';

type SizingMode = 'fixed' | 'dynamic';

Expand All @@ -27,6 +28,7 @@ export const StrategyComparison = memo(function StrategyComparison({
onChange,
projectionYears,
}: StrategyComparisonProps) {
useBrandRevealed();
const years = projectionYears ?? 10;

// Per-strategy sizing modes (defaults to the base input's sizing mode)
Expand Down Expand Up @@ -131,7 +133,7 @@ export const StrategyComparison = memo(function StrategyComparison({
Compare different strategies side-by-side to find the best fit for your situation. Select
2-3 strategies below.
{baseInputs.qfafEnabled &&
' Each strategy can use Fixed or Dynamic QFAF sizing independently.'}
brandText(' Each strategy can use Fixed or Dynamic QFAF sizing independently.')}
</p>

{/* Strategy Selector */}
Expand Down Expand Up @@ -211,7 +213,7 @@ export const StrategyComparison = memo(function StrategyComparison({
<tbody>
<tr className={getBest('qfafRequired') ? 'has-winner' : ''}>
<td>
<InfoText contentKey="comp-qfaf-required">QFAF Required</InfoText>
<InfoText contentKey="comp-qfaf-required">{brandText('QFAF Required')}</InfoText>
</td>
{comparisonResults.map(result => (
<td
Expand Down Expand Up @@ -311,7 +313,7 @@ export const StrategyComparison = memo(function StrategyComparison({
if (!best) return 'N/A';
return `${best.strategyName} (${best.sizingMode})`;
})()}{' '}
(lowest QFAF required)
{brandText('(lowest QFAF required)')}
</p>
</div>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { QfafTestPage } from './pages/QfafTestPage';
import { WorkspaceTab } from './workspace/WorkspaceTab';
import { ThemeToggle } from './components/ThemeToggle';
import { ErrorBoundary } from './components/ErrorBoundary';
import { brandText, useBrandRevealed } from './branding';

type View = 'calculator' | 'workspace' | 'qfaf-test';

Expand All @@ -19,6 +20,7 @@ function getInitialView(): View {
}

export function App() {
useBrandRevealed();
const [activeView, setActiveView] = useState<View>(getInitialView);

// Allow dev access to QFAF Test via Ctrl+Shift+Q
Expand Down Expand Up @@ -51,7 +53,7 @@ export function App() {
</button>
{activeView === 'qfaf-test' && (
<button className={`nav-tab active`} onClick={() => setActiveView('qfaf-test')}>
QFAF Test
{brandText('QFAF Test')}
</button>
)}
<ThemeToggle />
Expand Down
8 changes: 6 additions & 2 deletions src/Calculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import { ScenarioAnalysis } from './AdvancedMode/ScenarioAnalysis';
import { StrategyComparison } from './AdvancedMode/StrategyComparison';
import { MeetingMode } from './components/MeetingMode/MeetingMode';
import { brandText, useBrandRevealed } from './branding';

// Generate default year overrides for 10 years
const generateDefaultOverrides = (baseIncome: number): YearOverride[] => {
Expand All @@ -68,6 +69,7 @@
}

export function Calculator({ isActive = true }: CalculatorProps) {
useBrandRevealed();
const [inputs, setInputs] = useState<CalculatorInputs>(DEFAULTS);
const advancedMode = useAdvancedMode();
const { isExpanded } = useScrollHeader('scroll-sentinel');
Expand Down Expand Up @@ -138,7 +140,7 @@
return calculateWithSensitivity(inputs, advancedSettings, sensitivityParams);
}
return calculate(inputs, advancedSettings);
}, [

Check warning on line 143 in src/Calculator.tsx

View workflow job for this annotation

GitHub Actions / checks

React Hook useMemo has an unnecessary dependency: 'rateVersion'. Either exclude it or remove the dependency array
inputs,
advancedSettings,
rateVersion,
Expand Down Expand Up @@ -414,9 +416,11 @@
<span>Meeting Mode</span>
</button>
<h1>Tax Optimization Calculator</h1>
<p className="subtitle">QFAF + Collateral Strategy</p>
<p className="subtitle">{brandText('QFAF + Collateral Strategy')}</p>
<p className="header-description">
Model tax-loss harvesting strategies with QFAF overlays and collateral optimization.
{brandText(
'Model tax-loss harvesting strategies with QFAF overlays and collateral optimization.'
)}
</p>
<div className="header-feedback-banner">
This tool is in active development. Feedback and suggestions are welcome!{' '}
Expand Down
3 changes: 3 additions & 0 deletions src/InfoPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState } from 'react';
import { createPortal } from 'react-dom';
import { getPopupContent } from './popupContent';
import { useBrandRevealed } from './branding';

interface InfoPopupProps {
title: string;
Expand Down Expand Up @@ -114,6 +115,7 @@ interface FieldInfoPopupProps {
}

export function FieldInfoPopup({ contentKey, currentValue, breakdown }: FieldInfoPopupProps) {
useBrandRevealed(); // re-render when the brand-reveal toggle flips
const content = getPopupContent(contentKey);
if (!content) return null;

Expand All @@ -134,6 +136,7 @@ interface InfoTextProps {

export function InfoText({ contentKey, children, currentValue, breakdown }: InfoTextProps) {
const [isOpen, setIsOpen] = useState(false);
useBrandRevealed(); // re-render when the brand-reveal toggle flips
const content = getPopupContent(contentKey);

// If no content, just render the children without popup functionality
Expand Down
29 changes: 19 additions & 10 deletions src/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BreakdownGranularity,
} from './calculations/lossBreakdown';
import { getEffectiveStLossRate } from './calculations/helpers';
import { brandText, useBrandRevealed } from './branding';

type ViewMode = 'combined' | 'qfaf-only' | 'collateral-only';

Expand Down Expand Up @@ -65,6 +66,7 @@ export function ResultsTable({
strategyId,
ltGainRate,
}: ResultsTableProps) {
useBrandRevealed();
const partialFirstYearMonths = startMonth > 1 ? 13 - startMonth : null;
const [expandPortfolio, setExpandPortfolio] = useState(false);
const [expandCapital, setExpandCapital] = useState(false);
Expand Down Expand Up @@ -222,14 +224,18 @@ export function ResultsTable({
<button
className={`view-mode-btn ${viewMode === 'qfaf-only' ? 'active' : ''}`}
onClick={() => setViewMode('qfaf-only')}
title="Quantinno Fundamental Arbitrage Fund — generates ordinary losses and short-term gains"
title={brandText(
'Quantinno Fundamental Arbitrage Fund — generates ordinary losses and short-term gains'
)}
>
QFAF Only
{brandText('QFAF Only')}
</button>
<button
className={`view-mode-btn ${viewMode === 'collateral-only' ? 'active' : ''}`}
onClick={() => setViewMode('collateral-only')}
title="Direct indexing collateral only (excludes QFAF ordinary loss benefits)"
title={brandText(
'Direct indexing collateral only (excludes QFAF ordinary loss benefits)'
)}
>
Collateral Only
</button>
Expand Down Expand Up @@ -287,7 +293,7 @@ export function ResultsTable({
</th>
) : viewMode === 'qfaf-only' ? (
<th className="col-portfolio qfaf-col">
<InfoText contentKey="col-qfaf-value">QFAF Value</InfoText>
<InfoText contentKey="col-qfaf-value">{brandText('QFAF Value')}</InfoText>
</th>
) : viewMode === 'collateral-only' ? (
<th className="col-portfolio collateral-col">
Expand All @@ -306,7 +312,7 @@ export function ResultsTable({
<InfoText contentKey="col-collateral-value">Collateral</InfoText>
</th>
<th className="col-detail qfaf-col">
<InfoText contentKey="col-qfaf-value">QFAF</InfoText>
<InfoText contentKey="col-qfaf-value">{brandText('QFAF')}</InfoText>
</th>
<th className="col-detail qfaf-col">
<InfoText contentKey="col-cash-returned">Cash Out</InfoText>
Expand Down Expand Up @@ -486,7 +492,7 @@ export function ResultsTable({
</span>
<InfoText contentKey="col-tax-savings">
{viewMode === 'qfaf-only'
? 'QFAF Benefit'
? brandText('QFAF Benefit')
: viewMode === 'collateral-only'
? 'Coll. Benefit'
: 'Savings'}
Expand Down Expand Up @@ -967,7 +973,9 @@ export function ResultsTable({
<th>Period</th>
<th>Deployment Year</th>
<th className="num">ST Losses (Collateral)</th>
{qfafEnabled && <th className="num">Ord. Losses (QFAF)</th>}
{qfafEnabled && (
<th className="num">{brandText('Ord. Losses (QFAF)')}</th>
)}
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -1094,6 +1102,7 @@ function TransposedTable({
qfafEnabled: boolean;
startMonth: number;
}) {
useBrandRevealed();
const showQfaf = viewMode === 'combined' || viewMode === 'qfaf-only';
const showCollateral = viewMode === 'combined' || viewMode === 'collateral-only';
const partialMonths = startMonth > 1 ? 13 - startMonth : null;
Expand Down Expand Up @@ -1144,7 +1153,7 @@ function TransposedTable({
...(showQfaf && qfafEnabled
? [
{
label: 'QFAF',
label: brandText('QFAF'),
contentKey: 'col-qfaf-value',
cell: (y: YearResult) => money(y.qfafValue),
},
Expand Down Expand Up @@ -1181,7 +1190,7 @@ function TransposedTable({
...(qfafEnabled && viewMode === 'combined'
? [
{
label: 'QFAF ST Gains',
label: brandText('QFAF ST Gains'),
contentKey: 'col-st-gains',
cell: (y: YearResult) => (active(y) ? money(y.stGainsGenerated) : '—'),
},
Expand Down Expand Up @@ -1383,7 +1392,7 @@ function TransposedTable({
{
label:
viewMode === 'qfaf-only'
? 'QFAF Benefit'
? brandText('QFAF Benefit')
: viewMode === 'collateral-only'
? 'Coll. Benefit'
: 'Net Savings',
Expand Down
3 changes: 3 additions & 0 deletions src/WealthChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { YearResult } from './types';
import { formatCurrency, formatCurrencyAbbreviated } from './utils/formatters';
import { useDarkMode } from './hooks/useDarkMode';
import { brandText, useBrandRevealed } from './branding';

interface WealthChartProps {
data: YearResult[];
Expand Down Expand Up @@ -117,6 +118,7 @@ export const PortfolioValueChart = React.memo(function PortfolioValueChart({
startMonth,
}: WealthChartProps) {
const { isDark } = useDarkMode();
useBrandRevealed(); // re-render legend/tooltip when the brand-reveal toggle flips

// Memoize chart data transformation with confidence bands (007)
const chartData = useMemo(
Expand Down Expand Up @@ -243,6 +245,7 @@ export const PortfolioValueChart = React.memo(function PortfolioValueChart({
<Line
type="monotone"
dataKey="QFAF Value"
name={brandText('QFAF Value')}
stroke="#6366f1"
strokeWidth={2}
dot={{ r: 3 }}
Expand Down
Loading
Loading