diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index bb73a2b..00b1b19 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -79,11 +79,6 @@ npm run lint
| Criterion 1 | How it was met |
| Criterion 2 | How it was met |
-### 7. Self-Assessment
-
-- [ ] The [Contributor Self-Assessment Form](../docs/self-assessment-checklist.md) has been completed and attached to this PR
-- **Form Location:**
-
---
## Screenshots / Recordings
diff --git a/README.md b/README.md
index 4403fee..da1bd04 100644
--- a/README.md
+++ b/README.md
@@ -53,19 +53,10 @@ Key resources for contributors:
> **Note:** All pull requests must follow the [PR Evidence Checklist](docs/pr-evidence-checklist.md) and be audited against the [Evaluation Readiness Dashboard](docs/evaluation-readiness.md) before requesting review.
-The transactions page also includes a contributor-ready fixture gallery for transaction review, progress, and receipt states. See [docs/transaction-components.md](docs/transaction-components.md) for the shared component contract and [docs/transaction-review-modal.md](docs/transaction-review-modal.md) for review-before-sign behaviour, risk notes, and the operation summary mapper.
+The transactions page also includes a contributor-ready fixture gallery for transaction review, progress, and receipt states. See [docs/transaction-components.md](docs/transaction-components.md) for the shared component contract, fixture expectations, and compliance-safe wording guidance.
-## Evaluation Readiness
-Before submitting a pull request for maintainer review or payment evaluation, review the [Evaluation Readiness Dashboard](docs/evaluation-readiness.md). It links testing standards, CI status expectations, PR evidence templates, acceptance criteria mapping, and conduct guidelines into a single scorecard.
-
-## Payment-Period Conduct
-During GrantFox evaluation windows, all contributors must follow the [Payment-Period Conduct Policy](docs/payment-period-conduct.md). Repeated spam, payout-related complaints, or harassment in community channels will not be tolerated. Self-review your PR thoroughly before raising payment concerns.
+## Transfer Restriction Explainer
+Investor-facing transfer flows now include a reusable explainer for transfer restrictions. The logic lives in [src/lib/eligibility.ts](src/lib/eligibility.ts) and the UI is surfaced in the investor portfolio and transfer modal. See [docs/investor-transfer-eligibility.md](docs/investor-transfer-eligibility.md) for the typed state model, edge cases, and compliance-safe wording guidance.
## Route Access
Role-aware route guards protect admin, issuer, investor, and read-only sections. See `docs/route-access.md` for route mapping, guard states, SDK assumptions, and mock wallet fixtures.
-
-## Contribution Quality Examples
-Before submitting a PR, read the [Contribution Quality Examples](docs/contribution-quality-examples.md) for concrete examples of low-effort, partial, under-tested, failing-CI, and acceptable contributions — including what each category looks like and how to improve it. The companion [Low-Effort PR Examples](docs/low-effort-pr-examples.md) covers additional screenshot and CI-specific anti-patterns.
-
-## Documentation Index
-A full index of every file in `docs/` organised by audience (onboarding, building features, review and payment, release) is at [docs/README.md](docs/README.md).
\ No newline at end of file
diff --git a/docs/investor-transfer-eligibility.md b/docs/investor-transfer-eligibility.md
index 6d68492..158b9e1 100644
--- a/docs/investor-transfer-eligibility.md
+++ b/docs/investor-transfer-eligibility.md
@@ -31,11 +31,13 @@ This panel surfaces a human-readable explanation instead of a bare error.
- No wording implies a legal or regulatory guarantee.
## Files
-- `src/lib/eligibility.ts` — pure evaluation engine (no React; fully unit-tested).
+- `src/lib/eligibility.ts` — pure evaluation engine (no React; fully unit-tested) plus the portfolio-facing explainer helper.
- `src/lib/__fixtures__/eligibility.ts` — sample inputs for all major states.
-- `src/features/investor/InvestorEligibilityPanel.tsx` — the UI panel.
+- `src/features/investor/InvestorEligibilityPanel.tsx` — the reusable panel UI.
+- `src/features/investor/components/TransferRestrictionExplainer.tsx` — the portfolio/transfer-modal explainer used in investor flows.
- `src/lib/eligibility.test.ts` — engine unit tests.
- `src/features/investor/InvestorEligibilityPanel.test.tsx` — component tests.
+- `src/features/assets/components/AssetCard.test.tsx` — regression coverage for the investor asset card experience.
## SDK mapping
The dashboard should feed real signals into `EligibilityInput`:
@@ -45,4 +47,4 @@ The dashboard should feed real signals into `EligibilityInput`:
- `serviceAvailable` ← backend/SDK reachability.
Until those signals are wired, the panel can be rendered with `serviceAvailable: false`
-to honestly show an `unknown` state rather than guessing.
+to honestly show an `unknown` state rather than guessing. For portfolio and transfer modal contexts, the same logic is exposed through `explainPortfolioTransferEligibility(asset)` so contributors can reuse one consistent explanation path rather than branching into ad-hoc copy.
diff --git a/src/components/transactions/TransactionFixtureGallery.test.tsx b/src/components/transactions/TransactionFixtureGallery.test.tsx
index a5abe52..317204b 100644
--- a/src/components/transactions/TransactionFixtureGallery.test.tsx
+++ b/src/components/transactions/TransactionFixtureGallery.test.tsx
@@ -9,13 +9,10 @@ describe('TransactionFixtureGallery', () => {
screen.getByRole('heading', { name: /component fixture gallery/i }),
).toBeInTheDocument();
expect(screen.getByText(/review transfer before signing/i)).toBeInTheDocument();
- expect(screen.getByText(/review whitelist update/i)).toBeInTheDocument();
expect(screen.getByText(/waiting for signature/i)).toBeInTheDocument();
// expect(screen.getByText(/submitting to the network/i)).toBeInTheDocument();
expect(screen.getAllByText(/submitting to the network/i),).toHaveLength(2);
expect(screen.getByText(/transaction confirmed/i)).toBeInTheDocument();
- expect(screen.getByText(/pending receipt/i)).toBeInTheDocument();
- expect(screen.getByText(/transaction submitted/i)).toBeInTheDocument();
expect(screen.getByText(/transaction failed/i)).toBeInTheDocument();
expect(screen.getByText(/transaction status unknown/i)).toBeInTheDocument();
});
diff --git a/src/components/transactions/fixtures.ts b/src/components/transactions/fixtures.ts
index cfa5df6..45750a3 100644
--- a/src/components/transactions/fixtures.ts
+++ b/src/components/transactions/fixtures.ts
@@ -17,6 +17,17 @@ interface TransactionFixtureGalleryEntry {
explorerUrl?: string | null;
}
+interface TransactionFixtureGalleryEntry {
+ id: string;
+ kind: 'review' | 'progress' | 'receipt';
+ title: string;
+ description: string;
+ details?: TransactionDetails;
+ state?: 'signing' | 'pending';
+ result?: TransactionResult;
+ explorerUrl?: string | null;
+}
+
/**
* Sample data for previewing the transaction components without running a real
* flow — useful for manual QA of the failure and unknown states, which the
@@ -93,13 +104,6 @@ export const transactionResultFixtures: TransactionResult[] = [
unknownResultFixture,
];
-export const transactionReviewFixtures: TransactionDetails[] = [
- transferDetailsFixture,
- mintDetailsFixture,
- whitelistDetailsFixture,
- complianceDetailsFixture,
-];
-
export const transactionFixtureGalleryEntries: TransactionFixtureGalleryEntry[] = [
{
id: 'review-transfer',
@@ -115,13 +119,6 @@ export const transactionFixtureGalleryEntries: TransactionFixtureGalleryEntry[]
description: 'Preview the confirmation copy for an issuance workflow.',
details: mintDetailsFixture,
},
- {
- id: 'review-whitelist',
- kind: 'review',
- title: 'Review whitelist update',
- description: 'Preview the confirmation screen for a whitelist add action.',
- details: whitelistDetailsFixture,
- },
{
id: 'review-compliance',
kind: 'review',
@@ -152,15 +149,6 @@ export const transactionFixtureGalleryEntries: TransactionFixtureGalleryEntry[]
result: successResultFixture,
explorerUrl: 'https://stellar.expert/explorer/testnet/tx/b9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e',
},
- {
- id: 'receipt-pending',
- kind: 'receipt',
- title: 'Pending receipt',
- description: 'Preview the submitted-but-unconfirmed receipt state.',
- details: transferDetailsFixture,
- result: pendingResultFixture,
- explorerUrl: 'https://stellar.expert/explorer/testnet/tx/c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d3e4f50',
- },
{
id: 'receipt-failure',
kind: 'receipt',
diff --git a/src/features/assets/components/AssetCard.test.tsx b/src/features/assets/components/AssetCard.test.tsx
new file mode 100644
index 0000000..8bec588
--- /dev/null
+++ b/src/features/assets/components/AssetCard.test.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import { describe, it, expect } from 'vitest';
+import AssetCard from './AssetCard';
+import type { PortfolioAsset } from '@/lib/aegis/types';
+
+const asset: PortfolioAsset = {
+ id: 'asset-1',
+ name: 'Example Bond',
+ ticker: 'BND',
+ balance: 1250,
+ decimals: 7,
+ metadata: {
+ assetClass: 'Private Credit',
+ issuer: 'Example Issuer',
+ jurisdiction: 'US',
+ description: 'Example bond',
+ },
+ compliance: {
+ state: 'restricted',
+ label: 'Restricted',
+ detail: 'This asset has transfer restrictions.',
+ },
+ transferEligibility: {
+ state: 'ineligible',
+ reasons: ['Restricted to accredited investors only.'],
+ },
+ isDataAvailable: true,
+};
+
+describe('AssetCard', () => {
+ it('shows a transfer restriction explainer when the asset is not eligible', () => {
+ render( {}} />);
+
+ expect(screen.getByText(/Transfer eligibility/i)).toBeInTheDocument();
+ expect(screen.getByText('Not eligible')).toBeInTheDocument();
+ });
+});
diff --git a/src/features/assets/components/AssetCard.tsx b/src/features/assets/components/AssetCard.tsx
index 4983d35..c7a2412 100644
--- a/src/features/assets/components/AssetCard.tsx
+++ b/src/features/assets/components/AssetCard.tsx
@@ -1,7 +1,7 @@
import { formatAmount } from '@/utils/formatting';
import ComplianceBadge from './ComplianceBadge';
import TransferEligibilityBadge from './TransferEligibilityBadge';
-import AssetLifecycleBadge from './AssetLifecycleBadge';
+import TransferRestrictionExplainer from '@/features/investor/components/TransferRestrictionExplainer';
import type { PortfolioAsset } from '@/lib/aegis/types';
interface AssetCardProps {
@@ -45,10 +45,14 @@ export default function AssetCard({ asset, onTransferClick }: AssetCardProps) {