Files
ten31-database/start9/0.4/startos/utils.ts
T
Keysat d6250f74d0 Require a due date on all reminder creation (v0.1.0:103)
A date-less reminder has no urgency — it lands in the "Later"/"No date" bucket,
out of the overdue/today/this-week rollups and the daily digest — so every
create flow now pre-fills the due date to +1 week (editable) and blocks an empty
save. Shared reminderDefaultDue() helper; edit paths also pre-fill the default
for legacy date-less reminders.

Surfaces:
- Mobile: add-investor sheet (date auto-fills when you start the optional
  reminder), standalone Reminders "New reminder", Grid-detail "Set a reminder".
- Desktop: Reminders page "+ New reminder", grid reminder modal.

Server still accepts a null due_date by design (bot/automation callers); this is
a human-UI requirement. Frontend-only; no schema/migration/dependency change.
2026-06-20 16:51:03 -05:00

78 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Informational constants shared across the startos/ modules.
// The authoritative id, title and version for the package come
// from manifest/index.ts (id, title) and versions/ (version).
export const PACKAGE_ID = 'ten-database'
export const PACKAGE_TITLE = 'Ten31 Database'
// ExVer form of the current 0.4 wrapper release (upstream 0.1.0, wrapper rev 44).
// * 0.3.5 wrapper: 0.1.0.38 (legacy, aarch64)
// * First 0.4: 0.1.0:39 (shipped seed snapshot for migration)
// * Cleanup: 0.1.0:40 (seed removed + multi-threaded server + abuser auto-ban)
// * 0.1.0:41 (frontend persists auth across refreshes)
// * 0.1.0:42 (Gmail integration) / 0.1.0:43 (Gmail POST-body hotfix)
// * 0.1.0:44 (Phase-0 ingest + MCP server in image; build-index action)
// * 0.1.0:45 (Phase-1 thesis system; dual approval; merge review; in-app index)
// * 0.1.0:46 (packaging fix: ship full backend so migrations run + endpoints work)
// * 0.1.0:47 (soft-delete instead of hard-delete; source-count diagnostics)
// * 0.1.0:48 (entity model: investors vs people; fixes double-count)
// * 0.1.0:49 (Architect: Claude thesis generation + Thesis Workshop screen)
// * 0.1.0:50 (Set Anthropic API Key UI action — no terminal needed)
// * 0.1.0:51 (entity-resolution fix: people double-count + duplicate queue)
// * 0.1.0:52 (grid/contacts unification: contact_id link + grid as front door)
// * 0.1.0:53 (seed v5 thesis into the Architect Workshop)
// * 0.1.0:54 (unification polish: LinkedIn in grid inline contact editor)
// * 0.1.0:55 (Architect grounding boundary: redaction/re-hydration privacy gate)
// * 0.1.0:56 (Thesis Workshop redesign: edit/choose/delete + approve-as-current)
// * 0.1.0:57 (redaction fix: magnitude regex no longer eats the word after an amount)
// * 0.1.0:58 (seed 5 Architect positioning framings into the Workshop as candidate options)
// * 0.1.0:59 (Email Capture admin panel + matched email into the grounding corpus)
// * 0.1.0:60 (Email Capture: single-mailbox enroll field for testing)
// * 0.1.0:61 (Email Capture: live backfill progress + auto-refresh)
// * 0.1.0:62 (fix backfill crash on no-Reply-To emails; Sync now retries errored mailboxes)
// * 0.1.0:63 (System Status: storage usage — DB, attachments, backups, disk free)
// * 0.1.0:64 (email-activity agent: propose->review->approve grid notes; sync ~15 min)
// * 0.1.0:65 (Email Capture: per-mailbox captured/matched counts)
// * 0.1.0:66 (LP Objections page: UI trigger for the Architect grounding pass)
// * 0.1.0:67 (remove LP Objections page — generic/unverifiable; pivot to proactive outreach)
// * 0.1.0:68 (Outreach Draft Assistant — tailored LP drafts via thesis + redaction boundary)
// * 0.1.0:69 (follow-up radar — deterministic "needs attention" list + one-click draft)
// * 0.1.0:70 (outreach voice upgrade — per-user voice from own emails + transparency; active-thread context)
// * 0.1.0:71 (voice by-purpose larger sample + Tier-B: create Gmail draft w/ in-thread reply)
// * 0.1.0:72 (stage v2.0 reserve-asset thesis spine as Workshop candidates)
// * 0.1.0:73 (replace old settlement spine with v2.0 reserve-asset spine across Architect + outreach prompts, seed constants, and docs; promote v2.0 to the working approved spine + soft-retire old settlement nodes, reversibly, node-level only)
// * 0.1.0:74 (security/privacy hardening — full-eval P0+2×P1: close /assets/ path traversal, add NER backstop to the outreach redaction boundary, filter deleted_at on get-by-id)
// * 0.1.0:75 (Phase-A digest SMTP: per-package "Configure Digest SMTP" action writes /data/secrets/smtp/*; entrypoint exports SMTP_*; backend smtp_send.py + admin "send test email" endpoint + Settings→Admin "Send Test Digest Email" button)
// * 0.1.0:76 (digest send via Gmail DWD: backend/email_integration/gmail_send.py uses the existing service account's gmail.compose scope for users.messages.send; digest_mailer prefers Gmail DWD and falls back to SMTP; the admin test endpoint + Settings button route through it — no app password needed when Gmail is enabled)
// * 0.1.0:77 (daily activity digest — Phase B: digest_builder builds by-team-member [per-user Spark narrative, never Claude] + by-investor [inbound+outbound, deduped] sections; always-on digest_scheduler reads a DB-backed policy; enable/send-time in Settings→Admin via GET/PATCH /api/admin/digest/policy; POST /api/admin/digest/send-now + "Send Digest Now" button)
// * 0.1.0:78 (retire legacy lp_profiles + orphaned LP Tracker; Dashboard "Total Committed" repointed onto the fundraising grid [graveyard-excluded], "Total Funded" dropped; /api/lp-profiles* + lp-breakdown report removed; contact-dossier LP section + demo-seed LP block removed)
// * 0.1.0:79 (HOTFIX blank-screen: pin @babel/standalone@7.29.7 — the unpinned CDN upgraded to Babel 8, whose preset-react automatic JSX runtime emits an ESM import that blanks the classic inline-script app; plus close 3 server-side admin gaps: GET /api/users, /api/email/status, /api/email/accounts now require_admin)
// * 0.1.0:80 (repurpose Communications tab as the admin-only email-activity panel: new GET /api/email/activity [admin-enforced] over the email_* tables, filterable by investor/mailbox/direction + free-text search; classic manual log form retired; code-only, no schema change)
// * 0.1.0:81 (Communications tab is matched-only: query_email_activity gates on EXISTS email_investor_links, so unmatched cold/unknown-sender email is captured but never surfaced in the panel; code-only, no schema change)
// * 0.1.0:82 (vendor + SRI-pin the front-end libs: React/ReactDOM/Babel now ship in the s9pk and load same-origin from /assets/vendor/ with integrity hashes, so a CDN can never swap prod deps [the v78/v79 blank-screen class] and the box needs no outbound internet to render; plus a committed jsdom render smoke check [start9/0.4/render-smoke.mjs] gating the default `make` build)
// * 0.1.0:83 (email search/query + windowed digest preview, code-only: Communications investor dropdown now mirrors the list with typed keys [fund:/org:/contact:] so classic-contact/org-domain matches show + are pickable [fixes the empty-dropdown bug], plus a date-range filter, a click-to-expand full-body view [GET /api/email/detail], and a semantic "Search content" mode over indexed email bodies [GET /api/email/search -> ingest hybrid_search, soft-delete-filtered, 503 if Spark/Qdrant down]; Daily Digest gains an in-app windowed preview before send [POST /api/admin/digest/preview, send-now takes the same window] that exercises the real Spark summarizer without touching the daily cursor)
// * 0.1.0:84 (Matrix intake bot CRM support — ships the server side of commit 7ad0ee7, which was never packaged: new read-only GET /api/intake/match [new-vs-existing lookup against the canonical fundraising grid blob; returns the grid row id so an approved note lands on the matched investor, no duplicate] + source provenance on POST /api/fundraising/log-communication [audit records source, default "fundraising_grid"]; code-only, no schema change)
// * 0.1.0:85 (cosmetic: drop the redundant "[note]" tag from the fundraising-grid note line — now "YYYY-MM-DD Contact: summary"; informative comm types [call, meeting, …] keep their "[type]" tag; shared by the Matrix intake bot + grid-UI logging; no schema change)
// * 0.1.0:86 (Matrix intake fuzzy matching: GET /api/intake/match now returns ranked `candidates` [fuzzy near-matches — deterministic difflib name similarity + token overlap + email edit-distance ≤ 2, legal-suffix-aware] alongside the exact `match`, so the bot can surface near-duplicates ["Charlie"/"Charles", "Acme Capital"/"Acme Capital LLC", a one-char email typo] for human confirmation instead of silently creating a second investor; the bot-side disambiguation + conversational-edit UX ships on the Spark, not the s9pk; code-only, no schema change)
// * 0.1.0:87 (Adopt the Pipeline — grid drives the deal board: new "Add to Pipeline" row action creates+links an opportunity via opportunities.fundraising_investor_id [migration 0005, additive], reusing the grid's synced contact [no POST /api/contacts side-door] and mapping the grid lead→owner; idempotent [one live opp/investor, re-link never reseeds board-owned stage/probability]; read-only Pipeline + Pipeline Stage grid columns derived live from the linked opp; "Remove from Pipeline" soft-deletes the opp [grid row untouched]; deleting a grid investor archives its orphaned opp; folds in the soft-delete fix for the pipeline report + dashboard aggregates [archived opps no longer counted])
// * 0.1.0:88 (frontend-only: retire the Pipeline page's "+ New Opportunity" button + its create-by-contact modal — opportunities are now born only from a fundraising-grid investor row ["+ Pipeline"], so the board is a view + stage-management surface; replaced the button with a muted "Add deals from the Fundraising Grid" hint; removed the now-dead handler/state + the page's unused /api/contacts fetch)
// * 0.1.0:89 (email-proposal review over Matrix + a dedicated agent role: Email Capture's proposed grid notes gain a click-to-view inline popup of the source email [from/to/cc/date/subject/scrollable body, via the existing GET /api/email/detail]; and a CRM→Matrix review bridge — the intake bot [Spark] pulls pending proposals, posts a review card to a dedicated review room [MATRIX_EMAIL_REVIEW_ROOM], and relays in-thread yes/no/NL-edit back to the CRM, with web panel ↔ Matrix kept in sync [decide on either surface; the other reflects it]. New side table email_proposal_matrix [email-integration migration 0003, additive + idempotent] holds per-proposal Matrix thread state; new bot-or-admin endpoints GET /api/intake/email-proposals + .../{id}/matrix + .../{id}/decide, gated by a new 'bot' role [authenticated, never admin]. Bot poll loop + review-room handling ship on the Spark, not the s9pk)
// * 0.1.0:90 (give admins a UI path to provision the 'bot' role added in v89: the Settings → Admin edit-user role dropdown now offers "bot" alongside member/admin [the teammate-invite form stays member/admin only — provisioning an agent account is an admin re-classification, not an invite]; backend already accepted it; frontend-only, no schema change)
// * 0.1.0:91 (clarify email-proposal note wording: the proposed grid note now NAMES who emailed whom — "{teammate} emailed {investor}" outbound / "{sender} emailed the team" inbound — instead of a bare "Sent"/"Received"; also fixes a misclassification where a sender on our corporate domain whose mailbox isn't enrolled read as "Received" [outbound now also matches our domain, public providers like gmail excluded so an LP's gmail never reads as ours]; going-forward only, no schema change. Matrix-side review tweaks — dash separators + redacting decided cards — ship on the Spark, not the s9pk)
// * 0.1.0:92 (reminders & follow-ups, W1: new `reminders` table [in-app migration 0006; logical FK to fundraising_investors.id + denormalized name], full CRUD at /api/reminders [soft-delete; open/done/snoozed/cancelled; assignee; source human/bot/automation], read-only derived `reminder_status` grid column [overdue/due_soon/open — injected like pipeline_stage, filterable], orphan reconciler, Reminders page + Dashboard "Reminders Due" card + daily-digest "reminders due" section, and a per-investor last_activity_at recency rollup. Pure local CRM, no LLM path)
// * 0.1.0:93 (natural-language query, W2: read-only "ask the database in plain English" — a curated, parameterized query catalog [backend/nl_query/] behind a strict slot validator [the trust boundary — no generic SQL / dynamic identifiers]; a local-Qwen translator maps a question→{intent,slots} via Spark Control so the question never leaves the box [no Claude, no redaction]; new endpoints POST /api/query/nl + GET /api/query/catalog [require_bot_or_admin, audited entity_type='nl_query'], results never returned to any model; no schema change. The Matrix Q&A client [dedicated room + ?/@bot trigger] ships on the Spark, not the s9pk)
// * 0.1.0:94 (NL-query correctness fix: the comms_by_user + email_counts_by_user intents were counting/listing a user's ENTIRE captured sent corpus [internal/vendor/personal], not only email to a matched investor — they lacked the EXISTS email_investor_links gate that recent_emails + the Communications panel use. Added the matched-only gate to both [+ a regression test seeding an unmatched sent email]; no schema change, no UI change)
// * 0.1.0:95 (mobile-first redesign goes live + installable PWA. The Grid, Pipeline, Reminders & Contacts screens are touch-native on phones below 768px [safe-area bottom-tab nav, card lists, drag-dismiss bottom sheets, swipe actions, full-screen Grid detail, SVG tab icons + ·Ten31· wordmark], with an app-wide light theme + toggle. Installable home-screen PWA: manifest.webmanifest [standalone display, #0b1118 theme] + square/apple-touch icons + a pre-auth /manifest.webmanifest route; iOS-first, no service worker. Pipeline funnel v2: 4-stage lead→engaged→diligence→commitment [in-app migration 0007] with derived grid signals [pipeline_stage/existing_investor/recency] injected-on-GET, stripped-on-write. Desktop UI unchanged; no LLM path. Bundles the previously deploy-pending mobile Phases 08 + drag-reorder views + the PWA)
// * 0.1.0:96 (login page mobile/PWA conformance — the one surface the v95 mobile redesign skipped. CSS-only: 100vh→100dvh [dynamic viewport, fixes the centered card tucking under the iOS standalone status bar], a <768px media query adding 16px screen gutters + env[safe-area-inset] top/bottom clearance + touch-sized fields [inputs 46px/15px, button 46px], full-bleed card on small phones, and the §4 card depth shadow on the login card to match .section. No markup/JS/schema change; desktop login unchanged)
// * 0.1.0:97 (mobile top-bar polish + native zoom behaviour. Viewport meta gains maximum-scale=1 + user-scalable=no: kills pinch-zoom AND the iOS auto-zoom-on-focus that jerked the page in on every <16px input tap [app-wide, not just login]; OS accessibility zoom still works. Top-bar account initial now flex-centered + dc-aligned [IBM Plex Mono, accent-light, 13px — was defaulting to inline/baseline, off-center]. Quick-log pencil bumped --text-muted→--text-secondary for real affordance [the dc t3 grey thin-outline read as empty next to the color sun emoji on-device]. CSS-only; no JS/schema change)
// * 0.1.0:98 (business-card intake [Matrix bot] captures a contact's phone, mobile/cell, city + LinkedIn from a scanned card onto the contact record — cell to Mobile, office to Phone, fax skipped. Server half: _upsert_contact_from_fundraising now accepts phone+mobile on the contact dict [city+linkedin already worked]. The bot's transcription/extraction/card changes ship on the Spark [git pull + rebuild]. No schema change [contacts columns already exist]; no user-facing CRM change)
// * 0.1.0:99 (Grant device-test round 2, CRM half: intake fuzzy match scores DISTINCTIVE tokens only [no more "Investment Group"/"Capital"/"Family Office" false look-alikes]; mobile grid "Last contact"/staleness sort is reversible; mobile Edit-investor prefills a contact's email [GET /api/fundraising/state heals a blank grid pill from the linked classic contact, fill-only]; mobile quick-log pencil icon renders [CSS sizing on the sole flex-child svg]. The Matrix intake thread-redaction change ships on the Spark, not here. No schema change; no migration)
// * 0.1.0:100 (In-app business-card intake [#7]: a mobile camera button [left of the quick-log pencil] takes/picks a card photo, downscales it client-side via <canvas> to JPEG, and POSTs to the new POST /api/intake/card — vision-transcribe + parse + fuzzy-match on the box [local VL via Spark Control, nothing to Claude], reusing the Matrix card flow's nio-free parse/spark core. An editable review sheet [proposal fields + existing-investor picker] writes via log-communication tagged source="app_card"; a human approves every write. No schema change; no migration; no new dependency)
// * 0.1.0:101 (Mobile UX batch 1 [Grant device feedback]: [1] inline ✕ clear button on the Grid/Contacts search + reminder/quick-log investor pickers [ClearableInput]; [2] Grid investor-detail contact pills are tappable — name deep-links to the Contacts detail [new Grid→Contacts one-shot action], email opens mailto; [4a] mobile Pipeline is a full-height flex column so the whole area above the now bottom-pinned dots is the swipe target, each stage page scrolling its cards; [4b] expected-amount entry — optional amount when adding to the pipeline from the Grid detail [feeds pipeline/link], editable amount on the Pipeline card detail [PUT /api/opportunities/{id}]; [5] bottom sheets lift above the on-screen keyboard [visualViewport] so the reminder investor-picker results stay visible. Grid contact-name search [#3] already worked. CSS+React only; no schema change; no migration; no new dependency)
// * 0.1.0:102 (Mobile email-approval bell [#6]: an admin-only bell in the mobile top bar [left of the camera] with an iPhone-style count badge surfaces the SAME pending email-capture proposals the web "Email Capture" panel + the Matrix review room decide. Tap → card list of proposals → tap one → review screen [investor name + subject + summary + editable proposed note] → Approve & log to grid / Reject. Reuses the existing GET /api/activity/proposals + POST .../{id}/approve|dismiss [require_admin]; bidirectional sync is automatic — an app decision flips the proposal status and the bot's poll redacts the Matrix thread, while a Matrix/web decision drops the proposal from the pending list the bell polls [45s], clearing the badge. No LLM round-trip [edit-then-approve like the web panel]; mobile-gated so the hidden desktop top bar doesn't poll. Frontend-only; no schema change; no migration; no new dependency)
// * Current: 0.1.0:103 (Reminders require a due date [Grant feedback]: every reminder-create flow now pre-fills the due date to +1 week [editable] and blocks an empty save — a date-less reminder has no urgency [it falls to the "Later"/"No date" bucket, out of the overdue/today/this-week rollups + daily digest]. Applies to ALL create surfaces via a shared `reminderDefaultDue()` helper — mobile: the add-investor sheet [date auto-fills when you start the optional reminder], the standalone Reminders "New reminder" sheet, the Grid-detail "Set a reminder" card; desktop: the Reminders page "+ New reminder" + the grid reminder modal. Edit paths also pre-fill the default for legacy date-less reminders. Frontend-only; no schema/migration/dependency change)
export const PACKAGE_VERSION = '0.1.0:103'
export const DATA_MOUNT_PATH = '/data'
export const WEB_PORT = 8080
export const IMAGE_ID = 'main'
export const VOLUME_ID = 'main'