Merge origin/main (CI quality gates) into the 1.1.0 release

# Conflicts:
#	package-lock.json
#	src/views/NewInvoice.tsx
This commit is contained in:
2026-10-06 09:43:47 +05:30
52 changed files with 6100 additions and 1080 deletions
+1 -2
View File
@@ -102,7 +102,6 @@ export async function runE2eSteps(log: StepLog, reportPath: string): Promise<voi
},
);
let inv!: Invoice;
const issued = await log.run("e2e: issue and archive", async () => {
const outcome = await issueAndArchive(input, prefs, deps);
invoice = outcome.invoice;
@@ -112,7 +111,7 @@ export async function runE2eSteps(log: StepLog, reportPath: string): Promise<voi
};
});
if (!issued.ok || !invoice) return;
inv = invoice as Invoice;
const inv = invoice as Invoice;
await log.run("e2e: archive status", async () => {
const st = await api.archiveStatus(inv.id);
+1
View File
@@ -22,6 +22,7 @@ export function sanitize(value: string | null | undefined): string {
.replace(/\r\n?/g, "\n")
.replace(/[\t\n\v\f]/g, " ")
.replace(/ /g, " ")
// eslint-disable-next-line no-control-regex -- stripping control characters is the point
.replace(/[\u0000-\u001F\u007F-\u009F​]/g, "")
.replace(/ {2,}/g, " ")
.trim();
+1 -1
View File
@@ -21,7 +21,7 @@ import { RenderTxt } from "./blocks/RenderTxt";
import { TableGuard, guardPresence } from "./blocks/TableGuard";
import { renderCore } from "./render/core";
import { loadCanvas, inkPixels } from "./testing/rasterize";
import { byId, doc, FAMILY, find, frameFor, PAD, PAGE_W, rawLayout, txt } from "./testing/docs";
import { byId, doc, FAMILY, find, frameFor, PAD, rawLayout, txt } from "./testing/docs";
import { FONTS_DIR, setupPdfTest } from "./testing/setup";
import { stack } from "./fonts/register";
-2
View File
@@ -15,8 +15,6 @@ export const REVERSE_GAP = 4;
export const HEADING_AFTER = 4;
/** Space between the closing block's two columns. */
export const CLOSING_COLUMN_GAP = 16;
/** Slate: space between a mark and the business name in the dark band. */
const NAME_AFTER_LOGO = 7.8;
/** Space above and below the thin rule that separates the stack from TOTAL. */
export const STACK_RULE = { before: 2, after: 8, thickness: 0.75 };
const MIN_NAME_W = 40;
+2 -10
View File
@@ -1,17 +1,9 @@
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import type { AuditIssue } from "../engine/audit";
import { PAGE_SIZES } from "../engine/geometry";
import { PAGE_LABEL_ID, TABLE_HEAD_ID, TABLE_ROW_PREFIX, CLOSING_ID, TOTALS_ID } from "../engine/ids";
import { walk, type LayoutNode, type LayoutPage, type Rect } from "../engine/layoutTree";
import { forbiddenProbe, SLOT_RULES, slotContent, type SlotName } from "../model/slots";
import { frameForPrefs, renderInvoicePdf } from "../render/invoice";
import { loadCanvas, pagePng } from "../testing/rasterize";
import { SLOT_ID } from "../blocks/slots";
import { META_ID, SUPPLIER_ID, TITLE_ID } from "./classic/ids";
import { HEADER_GAP, headerCols } from "./classic/plan";
import { FIXTURE_GSTIN, FIXTURES, buildFixture, ensureSetup, makeItems, PAGE_SIZE_IDS, prefsFor, renderFixture, type FixtureSpec, type Rendered } from "./harness";
import { renderInvoicePdf } from "../render/invoice";
import { FIXTURES, buildFixture, ensureSetup, PAGE_SIZE_IDS, prefsFor, renderFixture, type FixtureSpec } from "./harness";
import { TEMPLATES } from "./registry";
/**
+1 -1
View File
@@ -1,6 +1,6 @@
import { Image, View } from "@react-pdf/renderer";
import type { ReactNode } from "react";
import { colsDetailed, pinned } from "../../engine/columns";
import { pinned } from "../../engine/columns";
import { CLOSING_ID, TABLE_HEAD_ID, TABLE_ROW_PREFIX, TOTALS_ID } from "../../engine/ids";
import { AvailableWidthProvider } from "../../blocks/AvailableWidth";
import { BottomSpacer } from "../../blocks/BottomSpacer";
+1 -1
View File
@@ -202,7 +202,7 @@ function Parties({ ctx }: { ctx: Ctx }) {
const blocks: ReactNode[] = [<ClientBlock key="a" ctx={ctx} width={text[0]} />];
let k = 1;
if (plan.info.hasBank) blocks.push(<PayableBlock key="b" ctx={ctx} width={text[k++]} />);
if (plan.info.hasIds) blocks.push(<IdsBlock key="c" ctx={ctx} width={text[k++]} />);
if (plan.info.hasIds) blocks.push(<IdsBlock key="c" ctx={ctx} width={text[k]} />);
const specs: ColSpec[] = cols.map((w, i) => (i === cols.length - 1 ? { fill: true } : { fixed: w }));
return (
<View style={{ ...pinned(plan.W), paddingLeft: v.inset.left } as never}>
+1 -1
View File
@@ -151,7 +151,7 @@ export function planMarble(model: RenderModel, frame: PageFrame, v: MarbleVarian
let k = 0;
const clientW = textW(k++);
const bankW = hasBank ? textW(k++) : 0;
const idsW = hasIds ? textW(k++) : 0;
const idsW = hasIds ? textW(k) : 0;
const head = leading(r.heading) + v.parties.headingGap;
let clientH = head + textHeight(r.body, c.name, clientW);
for (const l of c.addressLines) clientH += textHeight(r.body, l, clientW);
+1 -1
View File
@@ -295,7 +295,7 @@ export function planNeutral(model: RenderModel, frame: PageFrame, v: NeutralVari
for (const l of lines) payH += textHeight(r.address, l, payW);
}
if (v.id === "serenity") signH += leading(r.label) + 4;
let closingH = 0;
let closingH: number;
if (v.payTo.where === "closing") closingH = Math.max(payH, signH);
else closingH = signH;
if (model.cancelled) closingH += 10 + textHeight(r.small, CANCELLED_SENTENCE, W);
+1 -1
View File
@@ -11,7 +11,7 @@ import type { StepResult } from "./lib/selfTestSteps";
const WATCHDOG_MS = 120_000;
export async function maybeRunSelfTest(): Promise<void> {
let reportPath: string | null = null;
let reportPath: string | null;
try {
reportPath = await invoke<string | null>("selftest_config");
} catch {
-1
View File
@@ -24,7 +24,6 @@ import {
TabPanels,
Tabs,
Tile,
Toggle,
} from "@carbon/react";
import { Add, Save, TrashCan } from "@carbon/icons-react";
import { confirm } from "@tauri-apps/plugin-dialog";
+1 -7
View File
@@ -48,8 +48,7 @@ import type {
Settings,
TaxType,
} from "../lib/types";
import { computeLineAmount, computeTotals } from "../lib/invoice";
import { amountInWords } from "../lib/numberToWords";
import { computeLineAmount } from "../lib/invoice";
import { gstinFullError } from "../lib/validators";
import { addDays, formatAmount, todayIso } from "../lib/format";
import { modelFromAssets, prefsForInvoice } from "../lib/pdf";
@@ -265,11 +264,6 @@ export default function NewInvoice({ settings, onSettingsChange, active, onActiv
if (ok) badQuantities.current.delete(id);
else badQuantities.current.add(id);
};
const totals = useMemo(
() => computeTotals(items, discount, effectiveTaxType, taxRate),
[items, discount, effectiveTaxType, taxRate],
);
const words = useMemo(() => amountInWords(totals.total), [totals.total]);
const bank = useMemo(() => banks.find((b) => b.id === bankId) ?? null, [banks, bankId]);
const payload = useMemo(
+1 -1
View File
@@ -29,7 +29,7 @@ export default function SeriesSettings({
embedded?: boolean;
}) {
const toast = useToast();
const [active, setActive] = useState<InvoiceSeries | null>(null);
const [, setActive] = useState<InvoiceSeries | null>(null);
const [all, setAll] = useState<InvoiceSeries[]>([]);
const [loading, setLoading] = useState(true);