diff --git a/CLAUDE.md b/CLAUDE.md index 4c0f108..994f8ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,7 @@ node apps/hq/dist/server.cjs # first boot on an empty data/ prints a one-ti cd apps/hq-web && npm run dev ``` -- Owner login on first boot: `admin@tecnostac.com` + the one-time password printed in the +- Owner login on first boot: `admin@sims.com` + the one-time password printed in the server log (change it immediately). The "Gmail disconnected" banner is expected until Gmail is connected. - Env: `DATABASE_URL` (set → Postgres engine, absent → SQLite at `HQ_DATA_DIR`; D19), `SIMS_PIN_PEPPER` (set once, never change), `GOOGLE_*` (Gmail), `AWS_*` (Cost Explorer), diff --git a/README.md b/README.md index fbd59a4..d634ff0 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ npm install # from the repo root (workspaces: packages/*, ap npm run build --workspace @sims/hq node apps/hq/dist/server.cjs # serves :5182 (API, built web UI, /share/:token) # First boot on an empty data/ creates data/hq.db and prints a one-time owner -# password — capture it. Log in as admin@tecnostac.com, then change it. +# password — capture it. Log in as admin@sims.com, then change it. # Frontend — hq-web dev server (Vite on :5183, proxies /api and /share to :5182) cd apps/hq-web && npm run dev diff --git a/apps/hq/scripts/demo-seed.ts b/apps/hq/scripts/demo-seed.ts index 9998471..f2dfe24 100644 --- a/apps/hq/scripts/demo-seed.ts +++ b/apps/hq/scripts/demo-seed.ts @@ -1,5 +1,5 @@ /** - * Demo seed — populates a fresh HQ_DATA_DIR with a realistic Tecnostac dataset so the + * Demo seed — populates a fresh HQ_DATA_DIR with a realistic SiMS dataset so the * app can be shown with live screens. Run: HQ_DATA_DIR= npx tsx scripts/demo-seed.ts * Not shipped; a presentation helper. */ @@ -17,7 +17,7 @@ void (async () => { const db = openDb(process.env['HQ_DATA_DIR']) // Known owner so we can log in for screenshots (before seedIfEmpty, so it skips its own). - await createStaff(db, { email: 'owner@tecnostac.com', displayName: 'Thomas (Owner)', role: 'owner', password: 'demo-owner-2026' }) + await createStaff(db, { email: 'owner@sims.com', displayName: 'Thomas (Owner)', role: 'owner', password: 'demo-owner-2026' }) await seedIfEmpty(db) const U = 'demo' @@ -28,20 +28,20 @@ void (async () => { // Company + template letterhead data (what the Document Template page edits). const settings: Record = { - 'company.name': 'Tecnostac Solutions Pvt Ltd', + 'company.name': 'SiMS Solutions Pvt Ltd', 'company.address': '2nd Floor, Cyber Park, Kakkanad, Kochi, Kerala 682030', 'company.gstin': '32ABCDE1234F1Z9', 'company.state_code': '32', 'company.phone': '+91 98470 12345', - 'company.email': 'accounts@tecnostac.com', + 'company.email': 'accounts@sims.com', 'company.bank': 'HDFC Bank · A/c 50200012345678 · IFSC HDFC0001234 · Kakkanad', 'template.accent': '#1e3a5f', 'template.logo': LOGO, 'template.terms': '1. Prices are valid for 15 days from the date of this quotation.\n2. 50% advance on confirmation, balance before go-live.\n3. Annual subscription renews on the anniversary of activation.', 'template.declaration': 'We declare that this invoice shows the actual price of the services described and that all particulars are true and correct.', 'template.jurisdiction': 'Subject to Ernakulam jurisdiction', - 'template.footer_note': 'Thank you for your business. For support: support@tecnostac.com · +91 98470 12345', - 'template.signatory_label': 'For Tecnostac Solutions Pvt Ltd', + 'template.footer_note': 'Thank you for your business. For support: support@sims.com · +91 98470 12345', + 'template.signatory_label': 'For SiMS Solutions Pvt Ltd', } for (const [k, v] of Object.entries(settings)) await setSetting(db, U, k, v) @@ -100,7 +100,7 @@ void (async () => { const shareToken = ((await db.get<{ token: string }>(`SELECT token FROM document_share LIMIT 1`))!).token console.log('DEMO_READY') - console.log('login: owner@tecnostac.com / demo-owner-2026') + console.log('login: owner@sims.com / demo-owner-2026') console.log('quotation_id:', qtIssued.id, 'doc_no:', qtIssued.docNo) console.log('invoice_id:', inv1.id, 'doc_no:', inv1.docNo) console.log('share_token:', shareToken) diff --git a/apps/hq/src/seed.ts b/apps/hq/src/seed.ts index b8d75dc..8aac5b2 100644 --- a/apps/hq/src/seed.ts +++ b/apps/hq/src/seed.ts @@ -15,7 +15,7 @@ import { SCHEDULE_DEFAULTS, type ScheduleRuleKind } from './repos-reminders' const OWNER_EMAIL = process.env['HQ_OWNER_EMAIL']?.trim() || 'simssoftware13@gmail.com' const SETTING_DEFAULTS: Record = { - 'company.name': 'Tecnostac', + 'company.name': 'SiMS', 'company.address': '', 'company.gstin': '', 'company.state_code': '32', // Kerala — founder to confirm diff --git a/apps/hq/test/auth.test.ts b/apps/hq/test/auth.test.ts index 72a44b9..ccded21 100644 --- a/apps/hq/test/auth.test.ts +++ b/apps/hq/test/auth.test.ts @@ -6,9 +6,9 @@ import { createStaff, login, verifySession } from '../src/auth' describe('hq auth', () => { it('logs in with correct password, rejects wrong one', async () => { const db = openDb(':memory:') - await createStaff(db, { email: 'admin@tecnostac.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' }) - expect(await login(db, 'admin@tecnostac.com', 'wrong')).toMatchObject({ ok: false, reason: 'invalid' }) - const ok = await login(db, 'admin@tecnostac.com', 'let-me-in-9') + await createStaff(db, { email: 'admin@sims.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' }) + expect(await login(db, 'admin@sims.com', 'wrong')).toMatchObject({ ok: false, reason: 'invalid' }) + const ok = await login(db, 'admin@sims.com', 'let-me-in-9') expect(ok.ok).toBe(true) const staff = await verifySession(db, ok.ok ? ok.token : '') expect(staff).toMatchObject({ role: 'owner' }) diff --git a/apps/hq/test/bounces.test.ts b/apps/hq/test/bounces.test.ts index f97c8ad..b6ae53e 100644 --- a/apps/hq/test/bounces.test.ts +++ b/apps/hq/test/bounces.test.ts @@ -26,7 +26,7 @@ function bounceFetch(recipient: string): typeof fetch { describe('pollBounces', () => { it('flips the sent email_log to bounced and raises an email_bounced reminder, idempotently', async () => { const db = openDb(':memory:'); await seedIfEmpty(db) - await saveAccount(db, 'us@tecnostac.com', encrypt('rt', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('rt', KEY)) await logEmail(db, { to: 'ravi@acme.in', subject: 'INV/26-27-0001', status: 'sent', gmailMessageId: 'g1' }) const deps: BounceDeps = { gmail: { f: bounceFetch('ravi@acme.in'), clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, diff --git a/apps/hq/test/client-owner.test.ts b/apps/hq/test/client-owner.test.ts index f772b59..11943a3 100644 --- a/apps/hq/test/client-owner.test.ts +++ b/apps/hq/test/client-owner.test.ts @@ -17,7 +17,7 @@ import { apiRouter } from '../src/api' async function withOwner(): Promise<{ db: DB; ownerId: string }> { const db = openDb(':memory:') const { id } = await createStaff(db, { - email: 'admin@tecnostac.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9', + email: 'admin@sims.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9', }) return { db, ownerId: id } } diff --git a/apps/hq/test/convert-and-send.test.ts b/apps/hq/test/convert-and-send.test.ts index 71cac34..939197a 100644 --- a/apps/hq/test/convert-and-send.test.ts +++ b/apps/hq/test/convert-and-send.test.ts @@ -89,7 +89,7 @@ async function appWith(fetchImpl: typeof fetch) { const db = openDb(':memory:') const { c, m } = await base(db) await createStaff(db, { email: 'owner@test.in', displayName: 'Owner', role: 'owner', password: 'owner-password' }) - await saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) const pi = await issueDocument(db, 'u1', (await createDraft(db, 'u1', { docType: 'PROFORMA', clientId: c.id, lines: [{ moduleId: m.id, qty: 1, kind: 'yearly' }], })).id) diff --git a/apps/hq/test/dashboard.test.ts b/apps/hq/test/dashboard.test.ts index d293ba1..a995061 100644 --- a/apps/hq/test/dashboard.test.ts +++ b/apps/hq/test/dashboard.test.ts @@ -14,7 +14,7 @@ import { runDailyScan, type ScanDeps } from '../src/scheduler' const scanDeps: ScanDeps = { gmail: { f: (async () => new Response('{}')) as typeof fetch, clientId: '', clientSecret: '', keyHex: '' }, - renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), now: () => '2026-07-10T00:00:00Z', + renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z', } async function seeded() { diff --git a/apps/hq/test/due-dates.test.ts b/apps/hq/test/due-dates.test.ts index df160a6..9384f2c 100644 --- a/apps/hq/test/due-dates.test.ts +++ b/apps/hq/test/due-dates.test.ts @@ -13,7 +13,7 @@ import { runDailyScan, type ScanDeps } from '../src/scheduler' const deps: ScanDeps = { gmail: { f: (async () => new Response('{}')) as typeof fetch, clientId: '', clientSecret: '', keyHex: '' }, - renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), + renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-17T00:00:00Z', } @@ -122,7 +122,7 @@ describe('due dates (D18)', () => { const inv = await issueDocument(db, 'u1', (await invoiceDraft()).id) await db.run(`UPDATE document SET due_date='2026-07-02' WHERE id=?`, inv.id) const { id } = await upsertReminder(db, { ruleKind: 'invoice_overdue', subjectId: inv.id, duePeriod: 'd15', clientId: c.id, docId: inv.id, now: '2026-07-17T00:00:00Z' }) - const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'Tecnostac', '2026-07-17') + const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'SiMS', '2026-07-17') expect(ctx.dueDate).toBe('2026-07-02') expect(ctx.daysOverdue).toBe(15) expect(reminderEmail('invoice_overdue', ctx).bodyText).toMatch(/was due on 2026-07-02 and is now 15 day\(s\) overdue/) @@ -133,7 +133,7 @@ describe('due dates (D18)', () => { const inv = await issueDocument(db, 'u1', (await invoiceDraft()).id) await db.run(`UPDATE document SET doc_date='2026-07-07', due_date=NULL WHERE id=?`, inv.id) const { id } = await upsertReminder(db, { ruleKind: 'invoice_overdue', subjectId: inv.id, duePeriod: 'd7', clientId: c.id, docId: inv.id, now: '2026-07-17T00:00:00Z' }) - const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'Tecnostac', '2026-07-17') + const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'SiMS', '2026-07-17') expect(ctx.dueDate).toBeUndefined() expect(reminderEmail('invoice_overdue', ctx).bodyText).toMatch(/is outstanding and is now 10 day\(s\) past due/) }) diff --git a/apps/hq/test/employees.test.ts b/apps/hq/test/employees.test.ts index fd20cf1..f7479f0 100644 --- a/apps/hq/test/employees.test.ts +++ b/apps/hq/test/employees.test.ts @@ -12,7 +12,7 @@ import { listAudit } from '../src/audit' async function withOwner(): Promise<{ db: DB; ownerId: string }> { const db = openDb(':memory:') const { id } = await createStaff(db, { - email: 'admin@tecnostac.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9', + email: 'admin@sims.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9', }) return { db, ownerId: id } } diff --git a/apps/hq/test/module-roster.test.ts b/apps/hq/test/module-roster.test.ts index df1cb04..3cb3109 100644 --- a/apps/hq/test/module-roster.test.ts +++ b/apps/hq/test/module-roster.test.ts @@ -94,7 +94,7 @@ async function appWith(fetchImpl: typeof fetch) { const { db, m, mkClient } = await world() await createStaff(db, { email: 'owner@test.in', displayName: 'Owner', role: 'owner', password: 'owner-password' }) await createStaff(db, { email: 'staff@test.in', displayName: 'Staff', role: 'staff', password: 'staff-password' }) - await saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) const withMail = await mkClient('Acme Bank', 'ravi@acme.in') const noMail = await mkClient('Beta Coop') // no contact email — must be reported, not skipped silently await assignModule(db, 'u1', { clientId: withMail.id, moduleId: m.id, kind: 'yearly' }) diff --git a/apps/hq/test/preview-sample.test.ts b/apps/hq/test/preview-sample.test.ts index ffbf044..8a6ef55 100644 --- a/apps/hq/test/preview-sample.test.ts +++ b/apps/hq/test/preview-sample.test.ts @@ -17,7 +17,7 @@ const sampleClient = { id: 'c1', code: 'X', name: 'X', stateCode: '32', address: describe('templates: screen paper styles + documentHtmlSample', () => { it('documentHtml carries screen-only paper styles (PDF unaffected — print media)', () => { - expect(documentHtml(sampleDoc, sampleClient, { 'company.name': 'Tecnostac' })).toContain('@media screen') + expect(documentHtml(sampleDoc, sampleClient, { 'company.name': 'SiMS' })).toContain('@media screen') }) it('documentHtmlSample renders letterhead, bank box and given bullets through the one renderer', () => { const html = documentHtmlSample( diff --git a/apps/hq/test/quote-followup.test.ts b/apps/hq/test/quote-followup.test.ts index 4f273b1..64a7ad1 100644 --- a/apps/hq/test/quote-followup.test.ts +++ b/apps/hq/test/quote-followup.test.ts @@ -26,13 +26,13 @@ const okFetch = (async (url: string) => const deps: ScanDeps & SendReminderDeps = { gmail: { f: okFetch, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-fake'), - company: () => ({ 'company.name': 'Tecnostac' }), + company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T09:00:00Z', } async function world() { const db = openDb(':memory:'); await seedIfEmpty(db) - await saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) await setSetting(db, 'u1', 'share.base_url', 'https://hq.example.in') // sends refuse a dead relative link without it const c = await createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = await createModule(db, 'u1', { code: 'CBS', name: 'Core Banking' }) @@ -203,9 +203,9 @@ describe('quote_followup context, preview and send', () => { await runDailyScan(db, deps, '2026-07-04') const rem = (await followups(db))[0]! const auditBefore = ((await db.get<{ n: number }>(`SELECT COUNT(*) AS n FROM audit_log`))!).n - const { ctx } = await reminderContext(db, rem, 'Tecnostac') + const { ctx } = await reminderContext(db, rem, 'SiMS') const mail = reminderEmail('quote_followup', ctx) - expect(mail.subject).toContain('Tecnostac') + expect(mail.subject).toContain('SiMS') expect(await shareCount(db)).toBe(0) // resolve-only: preview NEVER mints expect(((await db.get<{ n: number }>(`SELECT COUNT(*) AS n FROM audit_log`))!).n).toBe(auditBefore) }) @@ -213,7 +213,7 @@ describe('quote_followup context, preview and send', () => { const { db, c, m } = await world() const q = await sentQuote(db, c, m, '2026-07-01T09:00:00Z') // never issued: docNo is null (F10) await runDailyScan(db, deps, '2026-07-04') - const { ctx } = await reminderContext(db, (await followups(db))[0]!, 'Tecnostac') + const { ctx } = await reminderContext(db, (await followups(db))[0]!, 'SiMS') const mail = reminderEmail('quote_followup', ctx) expect(mail.subject).not.toContain('null') expect(mail.bodyText).not.toContain('null') @@ -225,7 +225,7 @@ describe('quote_followup context, preview and send', () => { const q = await sentQuote(db, c, m, '2026-07-01T09:00:00Z') const issued = await issueDocument(db, 'u1', q.id) await runDailyScan(db, deps, '2026-07-04') - const { ctx } = await reminderContext(db, (await followups(db))[0]!, 'Tecnostac') + const { ctx } = await reminderContext(db, (await followups(db))[0]!, 'SiMS') expect(reminderEmail('quote_followup', ctx).subject).toContain(issued.docNo!) }) it('send resolves the dated subject/body on the injected clock, not the wall clock', async () => { @@ -238,9 +238,9 @@ describe('quote_followup context, preview and send', () => { await sentQuote(db, c, m, '2026-07-01T09:00:00Z') await runDailyScan(db, deps, '2026-07-04') const rem = (await followups(db))[0]! - const before = await reminderContext(db, rem, 'Tecnostac', '2026-07-10') + const before = await reminderContext(db, rem, 'SiMS', '2026-07-10') expect(reminderEmail('quote_followup', before.ctx).subject).not.toContain('FUTURE') - const after = await reminderContext(db, rem, 'Tecnostac', '2026-07-11') + const after = await reminderContext(db, rem, 'SiMS', '2026-07-11') expect(reminderEmail('quote_followup', after.ctx).subject).toContain('FUTURE') }) it('refuses to send when share.base_url is unset — loud error, NO share minted, row still queued', async () => { @@ -284,7 +284,7 @@ describe('quote_followup context, preview and send', () => { expect(out).toEqual({ ok: true }) expect(await shareCount(db)).toBe(1) // The sent body carries a live share link. - const { ctx } = await reminderContext(db, (await getReminder(db, first.id))!, 'Tecnostac') + const { ctx } = await reminderContext(db, (await getReminder(db, first.id))!, 'SiMS') expect(reminderEmail('quote_followup', ctx).bodyText).toContain('/share/') await runDailyScan(db, deps, '2026-07-08') // d7 const second = (await followups(db)).find((r) => r.duePeriod === 'd7')! diff --git a/apps/hq/test/receipt.test.ts b/apps/hq/test/receipt.test.ts index cb6e289..d4572e7 100644 --- a/apps/hq/test/receipt.test.ts +++ b/apps/hq/test/receipt.test.ts @@ -45,7 +45,7 @@ describe('payment receipts', () => { it('renders a receipt acknowledgment (no SAC/GST table)', async () => { const { db, c, inv } = await setup() const out = await recordPayment(db, 'u1', { clientId: c.id, receivedOn: '2026-07-10', mode: 'bank', amountPaise: 11_800_00, allocations: [{ documentId: inv.id, amountPaise: 11_800_00 }], issueReceipt: true }) - const html = documentHtml(out.receipt!, c, { 'company.name': 'Tecnostac' }) + const html = documentHtml(out.receipt!, c, { 'company.name': 'SiMS' }) expect(html).toContain('RECEIPT') expect(html).toContain('Received with thanks') expect(html).not.toContain('>SAC<') // the GST line table header is absent on receipts diff --git a/apps/hq/test/reminder-preview.test.ts b/apps/hq/test/reminder-preview.test.ts index 547f23c..7e6efc6 100644 --- a/apps/hq/test/reminder-preview.test.ts +++ b/apps/hq/test/reminder-preview.test.ts @@ -11,7 +11,7 @@ import { upsertReminder } from '../src/repos-reminders' import { apiRouter } from '../src/api' async function appWith() { - const db = openDb(':memory:'); await seedIfEmpty(db) // company.name='Tecnostac' + const db = openDb(':memory:'); await seedIfEmpty(db) // company.name='SiMS' await createStaff(db, { email: 'owner@test.in', displayName: 'Owner', role: 'owner', password: 'owner-password' }) const c = await createClient(db, 'u1', { name: 'Acme', code: 'ACME', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = await createModule(db, 'u1', { code: 'POS', name: 'POS' }) @@ -40,8 +40,8 @@ describe('GET /reminders/:id/preview', () => { const token = await login() const r = await get(token, ctx.overdueId) expect(r.status).toBe(200) - expect(r.json.subject).toContain(ctx.inv.docNo) // 'Payment reminder — Invoice INV/26-27-0001 (Tecnostac)' - expect(r.json.subject).toContain('Tecnostac') + expect(r.json.subject).toContain(ctx.inv.docNo) // 'Payment reminder — Invoice INV/26-27-0001 (SiMS)' + expect(r.json.subject).toContain('SiMS') expect(r.json.body).toContain('Acme') // clientName }) it('404s an unknown reminder and 400s an internal follow_up', async () => { diff --git a/apps/hq/test/reminders-page.test.ts b/apps/hq/test/reminders-page.test.ts index bbec794..2e5e2d0 100644 --- a/apps/hq/test/reminders-page.test.ts +++ b/apps/hq/test/reminders-page.test.ts @@ -23,13 +23,13 @@ const okFetch = (async (url: string) => const deps: ScanDeps & SendReminderDeps = { gmail: { f: okFetch, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-fake'), - company: () => ({ 'company.name': 'Tecnostac' }), + company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T09:00:00Z', } async function world() { const db = openDb(':memory:'); await seedIfEmpty(db) - await saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) await setSetting(db, 'u1', 'share.base_url', 'https://hq.example.in') const c = await createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = await createModule(db, 'u1', { code: 'CBS', name: 'Core Banking' }) diff --git a/apps/hq/test/reminders.test.ts b/apps/hq/test/reminders.test.ts index 7535bc6..bb068a0 100644 --- a/apps/hq/test/reminders.test.ts +++ b/apps/hq/test/reminders.test.ts @@ -9,7 +9,7 @@ import { describe('reminder templates', () => { it('renders an overdue dunning email with the amount and a polite tone', () => { const mail = reminderEmail('invoice_overdue', { - clientName: 'Malabar Stores', companyName: 'Tecnostac', + clientName: 'Malabar Stores', companyName: 'SiMS', docNo: 'INV/26-27-0007', amountPaise: 11_800_00, daysOverdue: 12, }) expect(mail.subject).toContain('INV/26-27-0007') diff --git a/apps/hq/test/scheduler-recurring.test.ts b/apps/hq/test/scheduler-recurring.test.ts index 59ca450..d3f0ba7 100644 --- a/apps/hq/test/scheduler-recurring.test.ts +++ b/apps/hq/test/scheduler-recurring.test.ts @@ -17,12 +17,12 @@ const okFetch = (async (url: string) => const deadFetch = (async () => new Response(JSON.stringify({ error: 'invalid_grant' }), { status: 400 })) as typeof fetch function deps(f: typeof fetch): ScanDeps { - return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), now: () => '2026-07-10T00:00:00Z' } + return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z' } } async function world() { const db = openDb(':memory:'); await seedIfEmpty(db) - await saveAccount(db, 'us@tecnostac.com', encrypt('rt', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('rt', KEY)) const c = await createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'R', email: 'r@acme.in' }] }) const m = await createModule(db, 'u1', { code: 'CLOUD', name: 'Cloud', allowedKinds: ['monthly'] }) await setPrice(db, 'u1', { moduleId: m.id, kind: 'monthly', pricePaise: 2_000_00, effectiveFrom: '2026-01-01' }) diff --git a/apps/hq/test/scheduler-scan.test.ts b/apps/hq/test/scheduler-scan.test.ts index 45b6498..9b790e4 100644 --- a/apps/hq/test/scheduler-scan.test.ts +++ b/apps/hq/test/scheduler-scan.test.ts @@ -14,7 +14,7 @@ import { runDailyScan, type ScanDeps } from '../src/scheduler' const deps: ScanDeps = { gmail: { f: (async () => new Response('{}')) as typeof fetch, clientId: '', clientSecret: '', keyHex: '' }, - renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), + renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z', } @@ -112,7 +112,7 @@ describe('invoice_overdue — dN milestone escalation (Phase 9, spec §8)', () = it('the email renders "N day(s) past due" from ctx.daysOverdue', async () => { const { db, c, inv } = await agedInvoice('2026-06-25') // 15 days before the 10th const { id } = await upsertReminder(db, { ruleKind: 'invoice_overdue', subjectId: inv.id, duePeriod: 'd15', clientId: c.id, docId: inv.id, now: '2026-07-10T00:00:00Z' }) - const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'Tecnostac', '2026-07-10') + const { ctx } = await reminderContext(db, (await getReminder(db, id))!, 'SiMS', '2026-07-10') expect(ctx.daysOverdue).toBe(15) expect(reminderEmail('invoice_overdue', ctx).bodyText).toMatch(/15 day\(s\) past due/) }) diff --git a/apps/hq/test/send-reminder.test.ts b/apps/hq/test/send-reminder.test.ts index ef39a99..11f6969 100644 --- a/apps/hq/test/send-reminder.test.ts +++ b/apps/hq/test/send-reminder.test.ts @@ -12,7 +12,7 @@ import { sendReminder, type SendReminderDeps } from '../src/send-reminder' const KEY = '11'.repeat(32) const fakePdf = async () => Buffer.from('%PDF-fake') -const company = () => ({ 'company.name': 'Tecnostac' }) +const company = () => ({ 'company.name': 'SiMS' }) function deps(f: typeof fetch): SendReminderDeps { return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: fakePdf, company, now: () => '2026-07-10T09:00:00Z' } @@ -20,7 +20,7 @@ function deps(f: typeof fetch): SendReminderDeps { async function invoiceSetup() { const db = openDb(':memory:'); await seedIfEmpty(db) - await saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + await saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) const c = await createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = await createModule(db, 'u1', { code: 'POS', name: 'POS' }) await setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-04-01' }) diff --git a/apps/hq/test/settings-company.test.ts b/apps/hq/test/settings-company.test.ts index 76d3f48..c004591 100644 --- a/apps/hq/test/settings-company.test.ts +++ b/apps/hq/test/settings-company.test.ts @@ -30,10 +30,10 @@ describe('company profile settings', () => { it('owner reads and updates company.* and it is audited', async () => { const token = await login('owner@test.in', 'owner-password') const before = await call(token, 'GET', '/settings/company') - expect(before.json.company['company.name']).toBe('Tecnostac') - const put = await call(token, 'PUT', '/settings/company', { name: 'Tecnostac Pvt Ltd', phone: '0484-1234567', stateCode: '32' }) + expect(before.json.company['company.name']).toBe('SiMS') + const put = await call(token, 'PUT', '/settings/company', { name: 'SiMS Pvt Ltd', phone: '0484-1234567', stateCode: '32' }) expect(put.status).toBe(200) - expect(put.json.company['company.name']).toBe('Tecnostac Pvt Ltd') + expect(put.json.company['company.name']).toBe('SiMS Pvt Ltd') expect(put.json.company['company.phone']).toBe('0484-1234567') const audits = (await ctx.db.get<{ n: number }>(`SELECT COUNT(*) AS n FROM audit_log WHERE entity='setting' AND entity_id LIKE 'company.%'`))! expect(audits.n).toBeGreaterThanOrEqual(3) diff --git a/apps/hq/test/settings-template.test.ts b/apps/hq/test/settings-template.test.ts index 4e8d642..69417c9 100644 --- a/apps/hq/test/settings-template.test.ts +++ b/apps/hq/test/settings-template.test.ts @@ -31,7 +31,7 @@ describe('template settings', () => { it('owner GET returns company.* + template.*; PUT writes template fields + a title, each audited', async () => { const token = await login('owner@test.in', 'owner-password') const before = await call(token, 'GET', '/settings/template') - expect(before.json.settings['company.name']).toBe('Tecnostac') + expect(before.json.settings['company.name']).toBe('SiMS') const put = await call(token, 'PUT', '/settings/template', { template: { footerNote: 'Thank you!', declaration: 'We declare…', signatoryLabel: 'Proprietor' }, titles: { INVOICE: 'GST INVOICE' }, diff --git a/apps/hq/test/template-accent.test.ts b/apps/hq/test/template-accent.test.ts index 6ad3b28..47e8800 100644 --- a/apps/hq/test/template-accent.test.ts +++ b/apps/hq/test/template-accent.test.ts @@ -14,7 +14,7 @@ const client = { id: 'c1', code: 'CL1', name: 'Acme', stateCode: '32', address: describe('template.accent (validated hex, single source of truth)', () => { it('defaults to slate-indigo #334155 when unset', () => { - const html = documentHtml(base, client, { 'company.name': 'Tecnostac' }) + const html = documentHtml(base, client, { 'company.name': 'SiMS' }) expect(html).toContain('--accent:#334155') }) it('applies a valid hex accent verbatim (6- and 3-digit)', () => { diff --git a/apps/hq/test/template-render.test.ts b/apps/hq/test/template-render.test.ts index e237139..7de69ad 100644 --- a/apps/hq/test/template-render.test.ts +++ b/apps/hq/test/template-render.test.ts @@ -14,7 +14,7 @@ const client = { id: 'c1', code: 'CL1', name: 'Acme', stateCode: '32', address: describe('template.* rendering (default-fallback)', () => { it('renders built-in defaults when template.* is unset (nothing breaks pre-config)', () => { - const html = documentHtml(invoice, client, { 'company.name': 'Tecnostac' }) + const html = documentHtml(invoice, client, { 'company.name': 'SiMS' }) expect(html).toContain('TAX INVOICE') // built-in title expect(html).toContain('Authorised Signatory') // built-in signatory label expect(html).not.toContain('class="declaration"') @@ -24,7 +24,7 @@ describe('template.* rendering (default-fallback)', () => { }) it('applies each template.* override where set', () => { const html = documentHtml(invoice, client, { - 'company.name': 'Tecnostac', + 'company.name': 'SiMS', 'template.title_INVOICE': 'GST INVOICE', 'template.declaration': 'We declare the particulars are true.', 'template.jurisdiction': 'Subject to Kochi jurisdiction', diff --git a/apps/hq/test/templates.test.ts b/apps/hq/test/templates.test.ts index feadf4b..df539ba 100644 --- a/apps/hq/test/templates.test.ts +++ b/apps/hq/test/templates.test.ts @@ -12,7 +12,7 @@ const doc = { } as never const client = { id: 'c1', code: 'CL0001', name: 'Acme', stateCode: '32', address: 'Kochi', contacts: [], status: 'active', notes: '' } as never -const company = { 'company.name': 'Tecnostac', 'company.gstin': '', 'company.address': '', +const company = { 'company.name': 'SiMS', 'company.gstin': '', 'company.address': '', 'company.phone': '', 'company.email': '', 'company.bank': 'HDFC ****1234' } describe('document html', () => { diff --git a/docs/DEPLOY-HQ.md b/docs/DEPLOY-HQ.md index 5e89396..cc1d45f 100644 --- a/docs/DEPLOY-HQ.md +++ b/docs/DEPLOY-HQ.md @@ -59,7 +59,7 @@ npm i -g pm2 env $(grep -v '^#' apps/hq/.env | xargs) pm2 start apps/hq/dist/server.cjs --name sims-hq pm2 save && pm2 startup ``` -Owner login on first boot is `admin@tecnostac.com` + the printed password (change it +Owner login on first boot is `admin@sims.com` + the printed password (change it immediately). The Gmail-disconnected banner is expected until step 6. ## 4. Put HTTPS in front (never expose Node directly) diff --git a/docs/superpowers/plans/2026-07-10-hq1-console.md b/docs/superpowers/plans/2026-07-10-hq1-console.md index fa5f10c..88037ca 100644 --- a/docs/superpowers/plans/2026-07-10-hq1-console.md +++ b/docs/superpowers/plans/2026-07-10-hq1-console.md @@ -411,9 +411,9 @@ import { createStaff, login, verifySession } from '../src/auth' describe('hq auth', () => { it('logs in with correct password, rejects wrong one', () => { const db = openDb(':memory:') - createStaff(db, { email: 'admin@tecnostac.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' }) - expect(login(db, 'admin@tecnostac.com', 'wrong')).toBeNull() - const ok = login(db, 'admin@tecnostac.com', 'let-me-in-9') + createStaff(db, { email: 'admin@sims.com', displayName: 'Owner', role: 'owner', password: 'let-me-in-9' }) + expect(login(db, 'admin@sims.com', 'wrong')).toBeNull() + const ok = login(db, 'admin@sims.com', 'let-me-in-9') expect(ok).not.toBeNull() const staff = verifySession(db, ok!.token) expect(staff).toMatchObject({ role: 'owner' }) @@ -912,7 +912,7 @@ const doc = { } as never const client = { id: 'c1', code: 'CL0001', name: 'Acme', stateCode: '32', address: 'Kochi', contacts: [], status: 'active', notes: '' } as never -const company = { 'company.name': 'Tecnostac', 'company.gstin': '', 'company.address': '', +const company = { 'company.name': 'SiMS', 'company.gstin': '', 'company.address': '', 'company.phone': '', 'company.email': '', 'company.bank': 'HDFC ****1234' } describe('document html', () => { @@ -1031,7 +1031,7 @@ describe('gmail connect', () => { **Interfaces:** - Produces: - - `seedIfEmpty(db): void` — if no `staff_user`: owner `admin@tecnostac.com` with a random 12-char password **printed to console once**; settings `company.*` placeholders + `company.state_code='32'`; `tax_class` row `('GST18', 1800, '2017-07-01')`. + - `seedIfEmpty(db): void` — if no `staff_user`: owner `admin@sims.com` with a random 12-char password **printed to console once**; settings `company.*` placeholders + `company.state_code='32'`; `tax_class` row `('GST18', 1800, '2017-07-01')`. - `stageCsv(db, kind: 'clients'|'invoices', csvText: string): { staged: number }` — tiny CSV parser (header row, comma, double-quote escaping — ~20 lines, no dependency); per-row problems collected into `problems` JSON: missing name, GSTIN checksum failure, bad date, duplicate code/doc_no. - `verificationReport(db): { clients: { staged: number; problems: number }; invoices: { staged: number; problems: number; totalPaise: number }; samples: { firstClients: string[]; lastInvoices: string[] } }` - `commitImport(db, userId): { clients: number; invoices: number; seeded: { fy: string; lastSeq: number } | null }` — inserts clients (`source='apex'`), invoices as issued documents (`source='apex'`, status `paid` when `paid=1` else `sent`, payload lines empty — history-only), then **seeds the INVOICE series**: parse the max numeric tail of current-FY staged doc numbers → `seedSeries(db,'INVOICE', fy, lastSeq)`. Refuses to run when any staged row has problems. diff --git a/docs/superpowers/plans/2026-07-10-hq2-reminders.md b/docs/superpowers/plans/2026-07-10-hq2-reminders.md index 079759e..d8fc243 100644 --- a/docs/superpowers/plans/2026-07-10-hq2-reminders.md +++ b/docs/superpowers/plans/2026-07-10-hq2-reminders.md @@ -1001,7 +1001,7 @@ import { describe('reminder templates', () => { it('renders an overdue dunning email with the amount and a polite tone', () => { const mail = reminderEmail('invoice_overdue', { - clientName: 'Malabar Stores', companyName: 'Tecnostac', + clientName: 'Malabar Stores', companyName: 'SiMS', docNo: 'INV/26-27-0007', amountPaise: 11_800_00, daysOverdue: 12, }) expect(mail.subject).toContain('INV/26-27-0007') @@ -1302,7 +1302,7 @@ import { sendReminder, type SendReminderDeps } from '../src/send-reminder' const KEY = '11'.repeat(32) const fakePdf = async () => Buffer.from('%PDF-fake') -const company = () => ({ 'company.name': 'Tecnostac' }) +const company = () => ({ 'company.name': 'SiMS' }) function deps(f: typeof fetch): SendReminderDeps { return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: fakePdf, company, now: () => '2026-07-10T09:00:00Z' } @@ -1310,7 +1310,7 @@ function deps(f: typeof fetch): SendReminderDeps { function invoiceSetup() { const db = openDb(':memory:'); seedIfEmpty(db) - saveAccount(db, 'us@tecnostac.com', encrypt('refresh-token', KEY)) + saveAccount(db, 'us@sims.com', encrypt('refresh-token', KEY)) const c = createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = createModule(db, 'u1', { code: 'POS', name: 'POS' }) setPrice(db, 'u1', { moduleId: m.id, kind: 'yearly', pricePaise: 10_000_00, effectiveFrom: '2026-04-01' }) @@ -1587,7 +1587,7 @@ import { runDailyScan, type ScanDeps } from '../src/scheduler' const deps: ScanDeps = { gmail: { f: (async () => new Response('{}')) as typeof fetch, clientId: '', clientSecret: '', keyHex: '' }, - renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), + renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z', } @@ -1789,12 +1789,12 @@ const okFetch = (async (url: string) => const deadFetch = (async () => new Response(JSON.stringify({ error: 'invalid_grant' }), { status: 400 })) as typeof fetch function deps(f: typeof fetch): ScanDeps { - return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), now: () => '2026-07-10T00:00:00Z' } + return { gmail: { f, clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z' } } function world() { const db = openDb(':memory:'); seedIfEmpty(db) - saveAccount(db, 'us@tecnostac.com', encrypt('rt', KEY)) + saveAccount(db, 'us@sims.com', encrypt('rt', KEY)) const c = createClient(db, 'u1', { name: 'Acme', stateCode: '32', contacts: [{ name: 'R', email: 'r@acme.in' }] }) const m = createModule(db, 'u1', { code: 'CLOUD', name: 'Cloud', allowedKinds: ['monthly'] }) setPrice(db, 'u1', { moduleId: m.id, kind: 'monthly', pricePaise: 2_000_00, effectiveFrom: '2026-01-01' }) @@ -2010,7 +2010,7 @@ function bounceFetch(recipient: string): typeof fetch { describe('pollBounces', () => { it('flips the sent email_log to bounced and raises an email_bounced reminder, idempotently', async () => { const db = openDb(':memory:'); seedIfEmpty(db) - saveAccount(db, 'us@tecnostac.com', encrypt('rt', KEY)) + saveAccount(db, 'us@sims.com', encrypt('rt', KEY)) logEmail(db, { to: 'ravi@acme.in', subject: 'INV/26-27-0001', status: 'sent', gmailMessageId: 'g1' }) const deps: BounceDeps = { gmail: { f: bounceFetch('ravi@acme.in'), clientId: 'cid', clientSecret: 'sec', keyHex: KEY }, @@ -2198,7 +2198,7 @@ import { runDailyScan, type ScanDeps } from '../src/scheduler' const scanDeps: ScanDeps = { gmail: { f: (async () => new Response('{}')) as typeof fetch, clientId: '', clientSecret: '', keyHex: '' }, - renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'Tecnostac' }), now: () => '2026-07-10T00:00:00Z', + renderPdf: async () => Buffer.from('%PDF-'), company: () => ({ 'company.name': 'SiMS' }), now: () => '2026-07-10T00:00:00Z', } function seeded() { diff --git a/docs/superpowers/plans/2026-07-10-hq3a-reports-costs.md b/docs/superpowers/plans/2026-07-10-hq3a-reports-costs.md index 0089fca..1ad630c 100644 --- a/docs/superpowers/plans/2026-07-10-hq3a-reports-costs.md +++ b/docs/superpowers/plans/2026-07-10-hq3a-reports-costs.md @@ -945,7 +945,7 @@ describe('payment receipts', () => { it('renders a receipt acknowledgment (no SAC/GST table)', () => { const { db, c, inv } = setup() const out = recordPayment(db, 'u1', { clientId: c.id, receivedOn: '2026-07-10', mode: 'bank', amountPaise: 11_800_00, allocations: [{ documentId: inv.id, amountPaise: 11_800_00 }], issueReceipt: true }) - const html = documentHtml(out.receipt!, c, { 'company.name': 'Tecnostac' }) + const html = documentHtml(out.receipt!, c, { 'company.name': 'SiMS' }) expect(html).toContain('RECEIPT') expect(html).toContain('Received with thanks') expect(html).not.toContain('>SAC<') // the GST line table header is absent on receipts @@ -1225,10 +1225,10 @@ describe('company profile settings', () => { it('owner reads and updates company.* and it is audited', async () => { const token = await login('owner@test.in', 'owner-password') const before = await call(token, 'GET', '/settings/company') - expect(before.json.company['company.name']).toBe('Tecnostac') - const put = await call(token, 'PUT', '/settings/company', { name: 'Tecnostac Pvt Ltd', phone: '0484-1234567', stateCode: '32' }) + expect(before.json.company['company.name']).toBe('SiMS') + const put = await call(token, 'PUT', '/settings/company', { name: 'SiMS Pvt Ltd', phone: '0484-1234567', stateCode: '32' }) expect(put.status).toBe(200) - expect(put.json.company['company.name']).toBe('Tecnostac Pvt Ltd') + expect(put.json.company['company.name']).toBe('SiMS Pvt Ltd') expect(put.json.company['company.phone']).toBe('0484-1234567') const audits = db.prepare(`SELECT COUNT(*) AS n FROM audit_log WHERE entity='setting' AND entity_id LIKE 'company.%'`).get() as { n: number } expect(audits.n).toBeGreaterThanOrEqual(3) diff --git a/docs/superpowers/plans/2026-07-13-live-preview.md b/docs/superpowers/plans/2026-07-13-live-preview.md index 5c079d0..d954147 100644 --- a/docs/superpowers/plans/2026-07-13-live-preview.md +++ b/docs/superpowers/plans/2026-07-13-live-preview.md @@ -562,7 +562,7 @@ const client = { id: 'c1', code: 'CL1', name: 'Acme', stateCode: '32', address: describe('template.* rendering (default-fallback)', () => { it('renders built-in defaults when template.* is unset (nothing breaks pre-config)', () => { - const html = documentHtml(invoice, client, { 'company.name': 'Tecnostac' }) + const html = documentHtml(invoice, client, { 'company.name': 'SiMS' }) expect(html).toContain('TAX INVOICE') // built-in title expect(html).toContain('Authorised Signatory') // built-in signatory label expect(html).not.toContain('class="declaration"') @@ -572,7 +572,7 @@ describe('template.* rendering (default-fallback)', () => { }) it('applies each template.* override where set', () => { const html = documentHtml(invoice, client, { - 'company.name': 'Tecnostac', + 'company.name': 'SiMS', 'template.title_INVOICE': 'GST INVOICE', 'template.declaration': 'We declare the particulars are true.', 'template.jurisdiction': 'Subject to Kochi jurisdiction', @@ -719,7 +719,7 @@ const sampleClient = { id: 'c1', code: 'X', name: 'X', stateCode: '32', address: describe('templates: screen paper styles + documentHtmlSample', () => { it('documentHtml carries screen-only paper styles (PDF unaffected — print media)', () => { - expect(documentHtml(sampleDoc, sampleClient, { 'company.name': 'Tecnostac' })).toContain('@media screen') + expect(documentHtml(sampleDoc, sampleClient, { 'company.name': 'SiMS' })).toContain('@media screen') }) it('documentHtmlSample renders letterhead, bank box and given bullets through the one renderer', () => { const html = documentHtmlSample( @@ -950,7 +950,7 @@ describe('template settings', () => { it('owner GET returns company.* + template.*; PUT writes template fields + a title, each audited', async () => { const token = await login('owner@test.in', 'owner-password') const before = await call(token, 'GET', '/settings/template') - expect(before.json.settings['company.name']).toBe('Tecnostac') + expect(before.json.settings['company.name']).toBe('SiMS') const put = await call(token, 'PUT', '/settings/template', { template: { footerNote: 'Thank you!', declaration: 'We declare…', signatoryLabel: 'Proprietor' }, titles: { INVOICE: 'GST INVOICE' }, @@ -1086,7 +1086,7 @@ import { upsertReminder } from '../src/repos-reminders' import { apiRouter } from '../src/api' function appWith() { - const db = openDb(':memory:'); seedIfEmpty(db) // company.name='Tecnostac' + const db = openDb(':memory:'); seedIfEmpty(db) // company.name='SiMS' createStaff(db, { email: 'owner@test.in', displayName: 'Owner', role: 'owner', password: 'owner-password' }) const c = createClient(db, 'u1', { name: 'Acme', code: 'ACME', stateCode: '32', contacts: [{ name: 'Ravi', email: 'ravi@acme.in' }] }) const m = createModule(db, 'u1', { code: 'POS', name: 'POS' }) @@ -1114,8 +1114,8 @@ describe('GET /reminders/:id/preview', () => { const token = await login() const r = await get(token, ctx.overdueId) expect(r.status).toBe(200) - expect(r.json.subject).toContain(ctx.inv.docNo) // 'Payment reminder — Invoice INV/26-27-0001 (Tecnostac)' - expect(r.json.subject).toContain('Tecnostac') + expect(r.json.subject).toContain(ctx.inv.docNo) // 'Payment reminder — Invoice INV/26-27-0001 (SiMS)' + expect(r.json.subject).toContain('SiMS') expect(r.json.body).toContain('Acme') // clientName }) it('404s an unknown reminder and 400s an internal follow_up', async () => { @@ -2121,7 +2121,7 @@ function QueueActions(props: { rem: Reminder & { docNo: string | null }; onDone: ``` - [ ] **Step 2: Browser-verify** (owner login → **Dashboard**, with at least one sendable reminder in the queue — an overdue invoice reminder is easiest to seed): - - Click **Preview** on an overdue reminder → the exact subject (e.g. `Payment reminder — Invoice INV/… (Tecnostac)`) and body appear inline, matching what Send would email; **Hide** collapses it. + - Click **Preview** on an overdue reminder → the exact subject (e.g. `Payment reminder — Invoice INV/… (SiMS)`) and body appear inline, matching what Send would email; **Hide** collapses it. - Internal-only rows (follow-up / bounced) show no Preview/Send button (only Dismiss), unchanged. - [ ] **Step 3: Typecheck + build + commit** — `npm run typecheck` + `npm run build -w @sims/hq-web` clean. diff --git a/docs/superpowers/plans/2026-07-17-hq-console-redesign.md b/docs/superpowers/plans/2026-07-17-hq-console-redesign.md index 73b8b9b..8850ea6 100644 --- a/docs/superpowers/plans/2026-07-17-hq-console-redesign.md +++ b/docs/superpowers/plans/2026-07-17-hq-console-redesign.md @@ -26,7 +26,7 @@ **Verify commands** (run from repo root `C:\SiMS\hq`): - Typecheck: `npm run typecheck` → exits 0 - Tests: `npm test` → all pass -- Dev app: terminal A `cd apps/hq && npm start` (API :5182), terminal B `cd apps/hq-web && npm run dev` (Vite :5183). Login `admin@tecnostac.com` (owner password known to the user; ask them if a login is needed for visual checks). +- Dev app: terminal A `cd apps/hq && npm start` (API :5182), terminal B `cd apps/hq-web && npm run dev` (Vite :5183). Login `admin@sims.com` (owner password known to the user; ask them if a login is needed for visual checks). --- diff --git a/docs/superpowers/specs/2026-07-17-hq-console-redesign-design.md b/docs/superpowers/specs/2026-07-17-hq-console-redesign-design.md index 0c68f23..36e5882 100644 --- a/docs/superpowers/specs/2026-07-17-hq-console-redesign-design.md +++ b/docs/superpowers/specs/2026-07-17-hq-console-redesign-design.md @@ -7,7 +7,7 @@ *Approved direction: "system upgrade" — Direction A v2's system discipline wearing H-04's warm palette. Sources reviewed: `admin_design_v2.html` (a Claude Design handoff tar; the -chosen "Direction A v2" system), `H-04 Client 360 (timeline ribbon).html`, `Tecnostac +chosen "Direction A v2" system), `H-04 Client 360 (timeline ribbon).html`, `SiMS Unified Workspace` promo, the small APEX card/loan-detail mockups, and the current `@sims/ui` tokens. Companion docs: `2026-07-17-apex-82963-ux-reference.md` (UX checklist honoured throughout), `2026-07-17-quote-to-close-funnel-design.md` (pages this shell must diff --git a/docs/superpowers/specs/2026-07-17-quote-to-close-funnel-design.md b/docs/superpowers/specs/2026-07-17-quote-to-close-funnel-design.md index c336ca9..e4211a2 100644 --- a/docs/superpowers/specs/2026-07-17-quote-to-close-funnel-design.md +++ b/docs/superpowers/specs/2026-07-17-quote-to-close-funnel-design.md @@ -108,7 +108,7 @@ rebuildTable(db, table, newDDL, guardToken): Employees [ + Add employee ] ──────────────────────────────────────────────────────────────────────────── Name Email Role Active Actions - Asha Rao asha@tecnostac.com owner ● Edit · Reset pw + Asha Rao asha@sims.com owner ● Edit · Reset pw Vikram Shah vikram@… manager ● Edit · Reset pw · Deactivate Priya Nair priya@… staff ○ Edit · Reset pw · Reactivate ────────────────────────────────────────────────────────────────────────────