## Summary PostgreSQL runtime roles without `CREATE` permission on `public` no longer trigger schema writes during migration-marker health reads, so `permission denied for schema public` is not mislabeled as database corruption. Once connectivity and task-ID integrity pass, an unavailable migration marker is treated as advisory instead of making the whole database unhealthy. Dashboard and notification guidance now describes a PostgreSQL health failure accurately and renders actionable log and recovery links in every supported locale. ## Validation - 54 targeted tests passed across core, dashboard, engine, and i18n. - Typechecks passed for all four affected packages. - Scoped ESLint, strict changeset validation, and diff checks passed. --- [](https://github.com/EveryInc/compound-engineering-plugin) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * PostgreSQL health failures are now reported as degraded health checks rather than database corruption. * Migration-status lookup failures no longer incorrectly mark an otherwise healthy database as unhealthy. * Migration-state checks are now read-only and avoid creating or modifying database structures. * **UI & Localization** * Updated database health banner messaging and recovery guidance across supported languages. * The banner now appears for broader PostgreSQL health failures and links to storage documentation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
import { AlertTriangle, RefreshCw } from "lucide-react";
|
|
import { Trans, useTranslation } from "react-i18next";
|
|
|
|
import "./DbCorruptionBanner.css";
|
|
|
|
interface DbCorruptionBannerProps {
|
|
errors: string[];
|
|
lastCheckedAt: string | null;
|
|
onRefresh: () => void | Promise<void>;
|
|
refreshing: boolean;
|
|
refreshError: string | null;
|
|
}
|
|
|
|
/*
|
|
FNXC:PostgresHealth 2026-07-19-17:55:
|
|
The shared degraded-health shape includes connectivity, permissions, and query
|
|
failures, so the banner must not claim corruption or prescribe SQLite recovery.
|
|
Keep operator guidance focused on PostgreSQL diagnostics and the reported error.
|
|
*/
|
|
export function DbCorruptionBanner({
|
|
errors,
|
|
lastCheckedAt,
|
|
onRefresh,
|
|
refreshing,
|
|
refreshError,
|
|
}: DbCorruptionBannerProps) {
|
|
const { t } = useTranslation("app");
|
|
if (errors.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const visibleErrors = errors.slice(0, 5);
|
|
const checkedAtLabel = lastCheckedAt ? new Date(lastCheckedAt).toLocaleString() : null;
|
|
|
|
return (
|
|
<section className="db-corruption-banner" role="alert" aria-live="assertive">
|
|
<div className="db-corruption-banner__header">
|
|
<div className="db-corruption-banner__headline-wrap">
|
|
<span className="status-dot status-dot--error" aria-hidden="true" />
|
|
<AlertTriangle aria-hidden="true" />
|
|
<div className="db-corruption-banner__headline-copy">
|
|
<h2 className="db-corruption-banner__headline">{t("dbBanner.title", "Database health check failed")}</h2>
|
|
{checkedAtLabel ? (
|
|
<p className="db-corruption-banner__meta">{t("dbBanner.lastChecked", "Last checked: {{checkedAtLabel}}", { checkedAtLabel })}</p>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="btn btn-sm db-corruption-banner__refresh"
|
|
onClick={() => {
|
|
void onRefresh();
|
|
}}
|
|
disabled={refreshing}
|
|
>
|
|
<RefreshCw className={refreshing ? "db-corruption-banner__refresh-icon db-corruption-banner__refresh-icon--spinning" : "db-corruption-banner__refresh-icon"} aria-hidden="true" />
|
|
{refreshing ? t("dbBanner.refreshing", "Refreshing…") : t("dbBanner.refreshHealth", "Refresh health")}
|
|
</button>
|
|
</div>
|
|
|
|
<p className="db-corruption-banner__body">
|
|
{t("dbBanner.body", "Fusion's PostgreSQL health check reported a failure. Review the details below before continuing critical operations.")}
|
|
</p>
|
|
|
|
<ul className="db-corruption-banner__list">
|
|
{visibleErrors.map((error, index) => (
|
|
<li key={`${index}:${error}`} className="db-corruption-banner__item">
|
|
<code className="db-corruption-banner__error-code">{error}</code>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
|
|
<p className="db-corruption-banner__footer">
|
|
<strong className="db-corruption-banner__footer-label">{t("dbBanner.whatToDo", "What to do:")}</strong>{" "}
|
|
<Trans
|
|
i18nKey="app:dbBanner.instructions"
|
|
defaults="Back up the project, check <cmd>database logs</cmd> and PostgreSQL connectivity and permissions, then refresh health. See <docsLink>docs/storage.md</docsLink> for storage and recovery guidance."
|
|
components={{
|
|
cmd: <code />,
|
|
docsLink: <a href="/docs/storage.md" />,
|
|
}}
|
|
/>
|
|
</p>
|
|
{refreshError ? <p className="db-corruption-banner__error">{refreshError}</p> : null}
|
|
</section>
|
|
);
|
|
}
|