Speed up the app, run production build, remove chunk ink, start App.jsx split

Performance:
- Lazy-load pages, docx, and mammoth (initial JS 1,298 KB -> ~290 KB)
- Gzip responses and cache hashed assets for a year
- Memoize DrawCanvas strokes so ink isn't redrawn on every render
- Precompute whole-Bible search index and defer the query while typing
- Reload once if a lazy chunk is missing after a deploy

Deployment:
- setup.sh builds and runs one production server on APP_PORT (default
  5173); --dev keeps the old Vite dev setup
- Session cookie uses secure: 'auto' so login works behind any proxy

Chunk editor:
- Remove the Draw layout, Ink Notes panel, and ink in HTML export
  (existing inkStrokes data is left in saved projects)

Refactor:
- Move module-level helpers out of App.jsx into src/utils/*
- Extract Bible reader state and logic into hooks/useBibleReader

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Opus 5.5 committed 2026-09-22 14:34:13 -04:00
1 parent 00932dee4a
commit ade7f1af6f
20 files changed
+1314 -1252

No files matched your search

+1 -28
View File
@@ -1,6 +1,4 @@
import { useState, useEffect } from 'react';
import { getStroke } from 'perfect-freehand';
import { pathFromStroke } from './inkRender.js';
// ---------------------------------------------------------------------------
// Study constants
@@ -192,28 +190,6 @@ export function getChunkVerseEntries(project, startChapterIndex, chunk) {
const escHtml = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
function inkStrokesToSvgHtml(strokes) {
if (!strokes?.length) return '';
const W = 700, H = 500;
const lines = [];
for (let y = 32; y < H; y += 32)
lines.push(`<line x1="0" y1="${y}" x2="${W}" y2="${y}" stroke="#dde3ec" stroke-width="1"/>`);
lines.push(`<line x1="40" y1="0" x2="40" y2="${H}" stroke="#fca5a5" stroke-width="1"/>`);
const paths = strokes.map((s) => {
const pts = s.points.map(([x, y, p]) => [x * W, y * H, p]);
const outline = getStroke(pts, {
size: s.size * H,
thinning: s.tool === 'highlighter' ? 0 : 0.5,
smoothing: 0.5,
streamline: 0.4,
});
const d = pathFromStroke(outline);
if (!d) return '';
return `<path d="${d}" fill="${escHtml(s.color)}" opacity="${s.tool === 'highlighter' ? '0.35' : '1'}"/>`;
}).filter(Boolean).join('');
return `<div class="ink-notes"><strong>INK NOTES:</strong><div style="margin-top:8px;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;background:#fff;"><svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}" style="display:block;max-width:100%;height:auto;">${lines.join('')}${paths}</svg></div></div>`;
}
export function buildExportHtml(project) {
const style = `
body { font-family: Georgia, serif; color: #0f172a; margin: 0; padding: 32px; }
@@ -224,7 +200,7 @@ export function buildExportHtml(project) {
.chunk { margin-bottom: 2rem; padding: 1.25rem 1.5rem; border: 1px solid #cbd5e1; border-radius: 0.75rem; background: #ffffff; }
.verse { margin: 0 0 0.75rem; line-height: 1.7; }
.scripture-ref { font-weight: 700; margin-bottom: 0.75rem; }
.oia, .cross-refs, .greek, .ink-notes { margin-top: 1rem; }
.oia, .cross-refs, .greek { margin-top: 1rem; }
.oia-section { margin-bottom: 0.75rem; }
.greek table { width: 100%; border-collapse: collapse; margin-top: 0.5rem; }
.greek th, .greek td { border: 1px solid #d1d5db; padding: 0.65rem; text-align: left; }
@@ -295,8 +271,6 @@ export function buildExportHtml(project) {
})
.join('');
const inkHtml = inkStrokesToSvgHtml(chunk.inkStrokes);
return `
<section class="chunk">
<div class="scripture-ref">${escHtml(scripture)}</div>
@@ -311,7 +285,6 @@ export function buildExportHtml(project) {
${greekRows ? greekTable : '<p><em>No Greek word notes.</em></p>'}
${extendedDefinitions}
</div>
${inkHtml}
</section>
`;
}).join('');