Skip to content
Open
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
32 changes: 27 additions & 5 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,34 @@
## Summary

Fixes #<!-- issue -->
<!-- Briefly describe the change and why it is needed. -->

## Test plan
## Related Issue

<!-- Example: Closes #71 -->

## Type Of Change

- [ ] Bug fix
- [ ] Feature
- [ ] Documentation
- [ ] Refactor or maintenance

## Testing

<!-- Paste the commands you ran and the result. -->

- [ ] `npm test`
- [ ] `npm run lint`
- [ ] `npm test`
- [ ] `npm run build`

## Contributor Checklist

- [ ] Branch name follows `type/area-slug` where possible.
- [ ] Tests were added or updated for new UI or behaviour.
- [ ] Documentation was updated for visible or API-facing changes.
- [ ] Accessibility was considered for keyboard, screen-reader, color contrast, and responsive states.
- [ ] No unrelated CI workflow changes are included.

## GrantFox
## Notes For Reviewers

Stellar payout wallet: `GBVHELLD2JE235Y2NGTDT3MWI3T65ON6SY4N6FBHYVDAQ5FZC2CP5QXH`
<!-- Include screenshots, rendered template previews, or follow-up notes if useful. -->
15 changes: 15 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ const config = {
"^@/(.*)$": "<rootDir>/src/$1",
},
collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.d.ts"],
// Issue #304: enforce a coverage floor for the most-touched paths so new
// untested code shows up as a coverage delta in the PR's CI summary.
// The thresholds are intentionally modest so this is an early-warning
// system, not a hard gate; tighten as the suite grows.
coverageThreshold: {
global: {
branches: 50,
functions: 60,
lines: 65,
statements: 65,
},
// Per-file overrides for paths with low surface area:
"./src/lib/format.ts": { branches: 80, functions: 100, lines: 100, statements: 100 },
"./src/lib/theme.ts": { branches: 70, functions: 90, lines: 90, statements: 90 },
},
};

module.exports = createJestConfig(config);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:coverage": "jest --coverage --coverageReporters=text-summary",
"test:coverage": "jest --coverage --coverageReporters=text-summary --coverageReporters=lcov",
"test:watch": "jest --watch"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/app/pairs/Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default function PairsClient() {
const [query, setQuery] = useState("");
const [pendingDelete, setPendingDelete] = useState<Pair | null>(null);

const pairs = status === "ok" ? data.pairs : null;
const pairs = status === "ok" && data ? data.pairs : null;
const filtered = useMemo(() => {
if (!pairs) return null;
const needle = query.trim().toLowerCase();
Expand Down
2 changes: 1 addition & 1 deletion src/app/settings/Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function RouterStatusRow() {
{status.error}
</p>
)}
{status.status === "ok" && (
{status.status === "ok" && status.data && (
<p className="text-sm">
Router is <strong>{status.data.paused ? "Paused" : "Live"}</strong>
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/app/stats/Client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function StatsClient() {
Loading…
</div>
)}
{status === "ok" && (
{status === "ok" && data && (
<section aria-labelledby="stats-metrics-heading">
<h2 id="stats-metrics-heading" className="sr-only">
Router metrics
Expand All @@ -40,7 +40,7 @@ export default function StatsClient() {
</dl>
</section>
)}
{status === "ok" && data.totalPairs === 0 && (
{status === "ok" && data && data.totalPairs === 0 && (
<EmptyState title="No pairs yet" description="Register a pair to see metrics." />
)}
</main>
Expand Down
21 changes: 19 additions & 2 deletions src/lib/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ type State<T> =
| { status: "error"; error: string }
| { status: "ok"; data: T };

export type UseApiResult<T> = State<T> & { refetch: () => void };
// Flatten the discriminated union so consumers can destructure
// `status`, `data`, and `error` from the result without TypeScript
// losing track of which fields are present on the current variant.
// Callers still narrow on `status` before reading the data-specific
// fields, but the type now exposes all three so a single-line
// `const { status, data, error } = useApi(...)` type-checks.
export type UseApiResult<T> = {
status: State<T>["status"];
data: T | null;
error: string | null;
refetch: () => void;
};

export function useApi<T>(path: string | null): UseApiResult<T> {
const [state, setState] = useState<State<T>>({ status: "loading" });
Expand Down Expand Up @@ -37,5 +48,11 @@ export function useApi<T>(path: string | null): UseApiResult<T> {
};
}, [path, reloadKey]);

return { ...state, refetch };
if (state.status === "ok") {
return { status: "ok", data: state.data, error: null, refetch };
}
if (state.status === "error") {
return { status: "error", data: null, error: state.error, refetch };
}
return { status: "loading", data: null, error: null, refetch };
}
2 changes: 1 addition & 1 deletion tsconfig.tsbuildinfo

Large diffs are not rendered by default.

Loading