diff --git a/README.md b/README.md index 1695543..f6dec80 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,88 @@ -# Checklists_Tools -Contains various tools used in specific work flows for Civil Engineering. Very niche +# GDOT Plan Tools + +**Browser-based productivity tools built for civil engineers working on Georgia DOT projects.** +No install. No login. Open the file, get to work. + +--- + +## What's Inside + +| Tool | Purpose | +|---|---| +| **FlowBoard** | Kanban project board with real-time cloud sync | +| **Section 24 Checklist** | Utility Plans — PPG compliance checklist | +| **Section 50 Checklist** | Erosion, Sedimentation & Pollution Control — PPG compliance checklist | +| **Section 60 Checklist** | Right of Way Plans — PPG compliance checklist | + +--- + +## FlowBoard + +A fast, focused Kanban board designed for managing the moving parts of GDOT project deliverables — submittals, reviews, revisions, coordination tasks, and everything in between. + +| Light theme | Dark theme | +|---|---| +| ![FlowBoard light theme](assets/screenshots/flowboard-demo-clear.png) | ![FlowBoard dark theme](assets/screenshots/flowboard-demo.png) | + +**Core features** + +- **Groups & columns** — Organize by project, phase, or discipline. Columns represent workflow stages (To Do → In Progress → Review → Done) +- **Cards with depth** — Each task carries a title, description, color label, priority flag, tags, due date, and a sub-item checklist +- **WIP limits** — Cap work-in-progress per column to surface bottlenecks before they become delays +- **Drag & drop** — Reorder cards and columns with fluid animations; collapsed columns pin as vertical pills to save screen space +- **Deadline tracking** — Overdue cards pulse red so nothing slips past a submission date +- **Search** — Filter cards across all groups instantly +- **Undo / Redo** — Full history stack; mistakes are reversible +- **15 themes** — Dark and light palettes including Midnight, Navy, Pearl, and more +- **Cloud sync** — Firebase Realtime Database keeps your board identical across devices; a safety gate blocks accidental overwrites from stale data +- **Backups** — Manual snapshots, auto-snapshots on idle, export/import JSON; up to 50 snapshots stored +- **Archive** — Move completed columns out of view without deleting them + +**Works entirely in the browser** — save the HTML file locally or host it on any static server. + +--- + +## PPG Compliance Checklists + +The GDOT [Plan Presentation Guide](https://gdot.ga.gov) defines exactly what must appear on every plan sheet before a set is accepted. Missing an item means a rejection, a resubmittal, and lost time. + +These checklists put every PPG requirement in front of you as an interactive list — organized by section, with the original wording preserved verbatim. Check items off as you review the plans; the progress bar tracks where you stand. + +**Section 24 — Utility Plans** +**Section 50 — Erosion, Sedimentation and Pollution Control Cover Drawing** +**Section 60 — Right of Way Plans** (Cover Drawing + Plan Drawings General) + +**Checklist features** + +- **Filter view** — Show All, Remaining only, or Done — focus on what's left without losing context +- **Progress tracking** — Live counter and progress bar per section and overall; sections badge green when complete +- **Collapsible sections** — Collapse sections you've finished to reduce visual noise +- **Auto-save** — Every check is written to localStorage instantly; reopen the file and pick up exactly where you left off +- **Snapshots** — Save named backups of your progress at any point; restore with one click +- **Export / Import** — Transfer progress between machines via JSON; useful when plans move between workstations or team members +- **Save as PNG / PDF** — Export a clean image or print-ready PDF of your completed checklist for the project file + +--- + +## How They Work Together + +1. **Open FlowBoard** and create a column for each phase of your submittal — *In Progress*, *Internal QC*, *Checking PPG*, *Ready to Submit* +2. When a plan set reaches *Checking PPG*, **open the relevant checklist** and work through it item by item +3. Once the checklist is complete, drag the card to *Ready to Submit* and export a PDF of the checklist as documentation +4. Reopen both files for the next review cycle — progress is remembered + +--- + +## Quick Start + +All tools are single HTML files. No build step, no dependencies to install. + +1. Clone or download this repository +2. Open any `.html` file directly in Chrome, Edge, or Firefox +3. For FlowBoard cloud sync, add your own Firebase Realtime Database credentials in the config block at the top of `flowboard.html` + +--- + +## Who This Is For + +Civil engineers, designers, and project managers at GDOT and consulting firms preparing construction plan sets for Georgia DOT submission. The checklists are keyed directly to the PPG — if you work on GDOT projects, these sections apply to your plans. diff --git a/Section 01 Checklist.html b/Section 01 Checklist.html new file mode 100644 index 0000000..ceea1b4 --- /dev/null +++ b/Section 01 Checklist.html @@ -0,0 +1,847 @@ + + + + + +Section 01 — Cover Drawing & Signature Drawing Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 01
+
+ + + Cover & Signature Drawing +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/Section 13 Checklist.html b/Section 13 Checklist.html new file mode 100644 index 0000000..dbeaad3 --- /dev/null +++ b/Section 13 Checklist.html @@ -0,0 +1,1015 @@ + + + + + +Section 13 - Mainline Roadway Plan Drawings Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 13
+
+ + + Mainline Roadway Plan Drawings Checklist +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/Section 24 Checklist.html b/Section 24 Checklist.html new file mode 100644 index 0000000..147ab65 --- /dev/null +++ b/Section 24 Checklist.html @@ -0,0 +1,695 @@ + + + + + +Section 24 - Utility Plans Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 24
+
+ + + Utility Plans Checklist +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/Section 50 Checklist.html b/Section 50 Checklist.html new file mode 100644 index 0000000..2a9536e --- /dev/null +++ b/Section 50 Checklist.html @@ -0,0 +1,686 @@ + + + + + +Section 50 - Erosion, Sedimentation and Pollution Control - Cover Drawing Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 50
+
+ + + Erosion, Sedimentation and Pollution Control - Cover Drawing Checklist +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/Section 54 Checklist.html b/Section 54 Checklist.html new file mode 100644 index 0000000..301d0eb --- /dev/null +++ b/Section 54 Checklist.html @@ -0,0 +1,792 @@ + + + + + +Section 54 — BMP Location Details Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 54
+
+ + + BMP Location Details +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/Section 60 Checklist.html b/Section 60 Checklist.html new file mode 100644 index 0000000..2244f84 --- /dev/null +++ b/Section 60 Checklist.html @@ -0,0 +1,716 @@ + + + + + +Section 60 - Right Of Way Plans Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section 60
+
+ + + Right Of Way Plans Checklist +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/assets/screenshots/flowboard-demo-clear.png b/assets/screenshots/flowboard-demo-clear.png new file mode 100644 index 0000000..72087f7 Binary files /dev/null and b/assets/screenshots/flowboard-demo-clear.png differ diff --git a/assets/screenshots/flowboard-demo.png b/assets/screenshots/flowboard-demo.png new file mode 100644 index 0000000..aa1c40f Binary files /dev/null and b/assets/screenshots/flowboard-demo.png differ diff --git a/data-safety-kit.html b/data-safety-kit.html new file mode 100644 index 0000000..2c137c8 --- /dev/null +++ b/data-safety-kit.html @@ -0,0 +1,1003 @@ + + + + +Data Safety Kit — portable backup/undo/sync module + + + + + + + +
+

🗄 Data Safety Kit

+
Standalone demo of the portable backup / undo / sync module. Open this file directly in a browser — nothing to install.
+ + + +
+ + + + 💾 Local +
+ +
+

Demo list

+
+ + +
+ +
+ +
+

Cloud sync (optional)

+
+ Firebase is not configured in this demo. Fill in DSK_CONFIG.firebaseConfig and set + DSK_CONFIG.firebaseEnabled = true in the source to enable it — every subsystem (undo, + snapshots, export/import, the modal below) already runs fully local-only with no cloud dependency. +
+
+
+ + +
+ + + + diff --git a/deployment-report.md b/deployment-report.md index 9ea23b6..c1c5cd7 100644 --- a/deployment-report.md +++ b/deployment-report.md @@ -1,8 +1,8 @@ -# FlowBoard — Sync & Android Deployment Report +# FlowBoard — Sync Deployment Report **Version:** 1.0 **Date:** March 2026 -**Scope:** Real-time cross-device sync via Firebase, Android PWA packaging, and Board Key access model +**Scope:** Real-time cross-device sync via Firebase and Board Key access model --- @@ -13,7 +13,6 @@ FlowBoard currently runs as a self-contained single HTML file using `localStorag - Real-time cross-device sync via Firebase Realtime Database - A Board Key access model (no user accounts required) - Offline support with automatic reconnect sync -- Android PWA installation (home screen, fullscreen, no Play Store required) The goal is to preserve the single-file nature of the desktop app while enabling it to optionally connect to a cloud backend when configured. @@ -343,91 +342,10 @@ The panel follows the same modal pattern already used throughout FlowBoard. --- -## 8. Android PWA - -### What Is a PWA? -A Progressive Web App is a website that Android Chrome can install to the home screen. Once installed it: -- Launches fullscreen with no browser chrome -- Has its own icon and splash screen -- Caches assets for offline launch -- Behaves like a native app - -### Required Files - -You need two additional files alongside the HTML file when hosting: - -**`manifest.json`** -```json -{ - "name": "FlowBoard", - "short_name": "FlowBoard", - "description": "Personal Kanban board with cloud sync", - "start_url": "/", - "display": "standalone", - "background_color": "#060d18", - "theme_color": "#060d18", - "icons": [ - { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, - { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" } - ] -} -``` - -**`sw.js`** (Service Worker — enables offline launch) -```javascript -var CACHE = 'flowboard-v1'; -var ASSETS = ['/', '/index.html', '/manifest.json']; - -self.addEventListener('install', function(e) { - e.waitUntil(caches.open(CACHE).then(function(c) { return c.addAll(ASSETS); })); -}); - -self.addEventListener('fetch', function(e) { - e.respondWith( - caches.match(e.request).then(function(r) { return r || fetch(e.request); }) - ); -}); -``` - -**In the HTML ``:** -```html - - - - -``` - -**Register the service worker (end of ``):** -```javascript -if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/sw.js'); -} -``` - -### Hosting Options - -The HTML file must be served over HTTPS for PWA installation to work. Free options: - -| Service | Notes | -|---|---| -| **GitHub Pages** | Free, HTTPS automatic, push to deploy. Ideal for this use case. | -| **Netlify** | Free tier, drag-and-drop deploy, HTTPS automatic | -| **Cloudflare Pages** | Free, very fast CDN, HTTPS automatic | - -For GitHub Pages: create a repo, push `index.html`, `manifest.json`, `sw.js`, and two icon PNGs, enable Pages in repo settings — done. - -### Installing on Android -1. Open the hosted URL in Chrome on Android -2. Chrome shows an **"Add to Home screen"** banner automatically after a few visits, or -3. Tap the three-dot menu → **Add to Home screen** -4. The app installs with an icon, opens fullscreen on next launch - ---- - -## 9. Data Flow Summary +## 8. Data Flow Summary ``` -[Desktop Browser] [Android PWA] +[Device A] [Device B] | | save() save() | | @@ -441,7 +359,7 @@ Both clients write to Firebase on every change. Both listen for remote changes. --- -## 10. Implementation Order +## 9. Implementation Order 1. Set up Firebase project and Security Rules (5 min, one-time) 2. Add Firebase SDK ` diff --git a/flowboard_docs/feature-groups.html b/flowboard_docs/feature-groups.html new file mode 100644 index 0000000..41e6ce0 --- /dev/null +++ b/flowboard_docs/feature-groups.html @@ -0,0 +1,505 @@ + + + + + + FlowBoard Feature Groups + + + +
+
+

FlowBoard Feature Groups

+

A grouped inventory of FlowBoard features. Use this as the roadmap for creating focused snippet pages that can be reused across other browser-based apps.

+
+
+ +
+ Back to FlowBoard Docs +
+ + +
+
+ First snippet group to build: State & Persistence. It is the best starting point because undo, snapshots, import/export, archive, and sync all depend on a clean state shape plus reliable save/load helpers. +
+ +
+

1. State & Persistence

+

The app foundation: state object shape, local persistence, state replacement, and schema checks.

+
+ Start here + Core dependency +
+

Features

+
    +
  • Global app state object, currently S
  • +
  • State load from localStorage
  • +
  • State save to localStorage
  • +
  • Last-modified timestamp updates
  • +
  • Default demo state seeding
  • +
  • Basic imported-state validation
  • +
  • Storage key naming conventions
  • +
  • Storage limit handling and warnings
  • +
+

Snippet targets

+
    +
  • loadState() and fallback defaults
  • +
  • saveState() with lastModified
  • +
  • validateState() for app-specific schemas
  • +
  • replaceState() for imports, restores, and remote sync
  • +
+
+ +
+

2. Recovery & History

+

Features that let users recover from mistakes or return to known-good states.

+
+ Next after state + User safety +
+

Features

+
    +
  • Undo stack
  • +
  • Redo stack
  • +
  • Session undo restore after refresh
  • +
  • Manual snapshots
  • +
  • Auto-snapshots at session start
  • +
  • Idle auto-snapshots
  • +
  • Before-destructive-action snapshots
  • +
  • Snapshot restore
  • +
  • Snapshot rename and delete
  • +
+

Snippet targets

+
    +
  • _pushUndo(), doUndo(), and doRedo()
  • +
  • createSnapshot() and restoreSnapshot()
  • +
  • createAutoSnapshot() trigger helpers
  • +
  • Undo button and shortcut wiring
  • +
+
+ +
+

3. Board Data Model

+

The reusable Kanban structure: groups contain columns, columns contain cards, and cards contain nested details.

+

Features

+
    +
  • Groups
  • +
  • Columns
  • +
  • Cards
  • +
  • Card checklist items
  • +
  • Archived columns and cards
  • +
  • Ungrouped columns
  • +
  • Stable IDs with uid()
  • +
+

Snippet targets

+
    +
  • Generic board state schema
  • +
  • ID generation
  • +
  • Find helpers for groups, columns, and cards
  • +
  • Counting helpers for stats and badges
  • +
+
+ +
+

4. Editing Workflows

+

Common UI operations for creating, editing, duplicating, deleting, and restoring user content.

+

Features

+
    +
  • Add group
  • +
  • Rename group inline
  • +
  • Delete group
  • +
  • Add column
  • +
  • Rename column inline
  • +
  • Delete column
  • +
  • Add card inline
  • +
  • Edit card title and description
  • +
  • Duplicate card
  • +
  • Delete card
  • +
  • Checklist add, edit, reorder, check, uncheck, clear done
  • +
+

Snippet targets

+
    +
  • Inline rename component pattern
  • +
  • Inline add form pattern
  • +
  • Confirm-before-delete wrapper
  • +
  • Checklist row editor
  • +
+
+ +
+

5. Drag & Drop

+

Pointer-based movement systems for cards and columns, plus visual drop indicators.

+

Features

+
    +
  • Card drag between columns
  • +
  • Card reorder within a column
  • +
  • Column drag between groups
  • +
  • Column reorder within a group
  • +
  • Ghost element creation
  • +
  • Drop indicator placement
  • +
  • Drag cancellation with Escape
  • +
  • Momentum scroll while dragging
  • +
  • Checklist item drag reorder inside modal
  • +
+

Snippet targets

+
    +
  • Pointer drag controller
  • +
  • Drop target detection using elementFromPoint()
  • +
  • Reusable drop indicator helpers
  • +
  • Move item between arrays
  • +
+
+ +
+

6. Task Metadata

+

Card-level attributes that make a task system useful beyond simple titles.

+

Features

+
    +
  • Priority levels
  • +
  • Color labels
  • +
  • Tags
  • +
  • Due dates
  • +
  • Due date status labels
  • +
  • Overdue styling
  • +
  • Description text
  • +
  • Checklist progress
  • +
+

Snippet targets

+
    +
  • Priority badge renderer
  • +
  • Tag add/remove helpers
  • +
  • Due date classifier
  • +
  • Checklist progress calculator
  • +
+
+ +
+

7. Organization & Limits

+

Features for keeping large boards readable and controlling work in progress.

+

Features

+
    +
  • Group collapse
  • +
  • Column collapse to vertical pill
  • +
  • Group-level card limits
  • +
  • Column priority sorting
  • +
  • Archived column hiding
  • +
  • Ungrouped fallback section
  • +
  • Stats chip for groups, columns, and cards
  • +
+

Snippet targets

+
    +
  • Collapsed state toggles
  • +
  • WIP limit application
  • +
  • Sort toggle and comparator
  • +
  • Active-only board selectors
  • +
+
+ +
+

8. Search & Filtering

+

Fast filtering helpers for finding cards and narrowing visible work.

+

Features

+
    +
  • Search by card title
  • +
  • Search by tags
  • +
  • Active card filtering
  • +
  • Archived item filtering
  • +
  • Checklist remaining/done filtering pattern from related checklist tools
  • +
+

Snippet targets

+
    +
  • Case-insensitive search helper
  • +
  • Tag-aware card matcher
  • +
  • Filter state object pattern
  • +
+
+ +
+

9. Theming & UI System

+

Reusable styling infrastructure for apps that need multiple visual modes without a build step.

+

Features

+
    +
  • CSS custom property theme tokens
  • +
  • Dark themes
  • +
  • Light themes
  • +
  • Theme preview on hover
  • +
  • Theme persistence
  • +
  • Palette-based group and column colors
  • +
  • Card color picker
  • +
  • Button style variants
  • +
  • Toast notifications
  • +
  • Animations for new cards, columns, groups, and modals
  • +
+

Snippet targets

+
    +
  • Theme token structure
  • +
  • Theme switcher with preview
  • +
  • Color picker pattern
  • +
  • Notification toast helper
  • +
+
+ +
+

10. Modals & Panels

+

Overlay and floating-panel systems used for focused editing, settings, and secondary workflows.

+

Features

+
    +
  • Card detail modal
  • +
  • Resizable modal dimensions
  • +
  • Unsaved changes banner
  • +
  • Unsaved draft item banner
  • +
  • Theme panel
  • +
  • Cloud sync modal
  • +
  • Backup and restore modal
  • +
  • Archive modal
  • +
  • Add group modal
  • +
  • Add column modal
  • +
  • Custom calendar dropdown
  • +
  • Tags dropdown
  • +
  • Column selector dropdown
  • +
+

Snippet targets

+
    +
  • Generic setModal() and closeModal()
  • +
  • Click-outside panel closer
  • +
  • Dirty-form close guard
  • +
  • Calendar dropdown component
  • +
+
+ +
+

11. Cloud Sync

+

Optional Firebase Realtime Database sync for keeping the same board state across devices.

+

Features

+
    +
  • Local-only mode
  • +
  • Board key creation
  • +
  • Join existing board by key
  • +
  • Saved board key list
  • +
  • Switch board key
  • +
  • Copy, email, rename, and remove board keys
  • +
  • Realtime remote listener
  • +
  • Last-write-wins conflict handling
  • +
  • Safety gate before remote writes
  • +
  • Debounced cloud writes
  • +
  • Offline and reconnect status handling
  • +
  • Cloud auto-snapshot merge
  • +
+

Snippet targets

+
    +
  • Sync status state machine
  • +
  • Debounced remote write helper
  • +
  • Safe-write guard
  • +
  • Board key manager
  • +
+
+ +
+

12. Exporting & Transfer

+

Features for moving board data or documentation outside the live app.

+

Features

+
    +
  • Export current board JSON
  • +
  • Export named snapshot JSON
  • +
  • Import board JSON
  • +
  • Download filename sanitization
  • +
  • Import confirmation
  • +
  • Pre-import auto-snapshot
  • +
  • Checklist PNG export pattern from checklist tools
  • +
  • Checklist PDF/print export pattern from checklist tools
  • +
+

Snippet targets

+
    +
  • JSON download helper
  • +
  • File picker and reader helper
  • +
  • State import pipeline
  • +
  • Print/export page setup
  • +
+
+ +
+

13. Quality & Safety

+

Defensive behaviors that prevent data loss and keep the app resilient.

+

Features

+
    +
  • Confirm dialogs before destructive actions
  • +
  • Auto-snapshot before destructive actions
  • +
  • Undo suppression during undo/redo
  • +
  • Undo reset after full-state replace
  • +
  • Remote write safety gate
  • +
  • Storage quota warning
  • +
  • Invalid import handling
  • +
  • Offline sync handling
  • +
  • Fallback behavior when Firebase is not configured
  • +
+

Snippet targets

+
    +
  • Safe destructive action wrapper
  • +
  • State replacement guard
  • +
  • Storage quota check
  • +
  • Remote safety validator
  • +
+
+
+
+
+ + diff --git a/flowboard_docs/feature-patterns.html b/flowboard_docs/feature-patterns.html new file mode 100644 index 0000000..5385da2 --- /dev/null +++ b/flowboard_docs/feature-patterns.html @@ -0,0 +1,666 @@ + + + + + + FlowBoard Feature Patterns + + + +
+
+

FlowBoard Feature Patterns

+

Reusable implementation notes for app features that are useful beyond FlowBoard: undo/redo, snapshots, auto-backups, import/export, and archive behavior.

+
+
+ +
+ Back to FlowBoard Docs +
+ + +
+
+

1. State Variables

+

These globals hold undo history, redo history, and auto-snapshot timing state. Keep them outside the saved app state unless you specifically want these mechanics to sync across devices.

+
// -- Undo / Redo --
+var _undoStack = [];        // array of JSON strings, full state snapshots, max 50
+var _redoStack = [];        // same shape as _undoStack, max 50
+var _undoInProgress = false; // prevents re-entrant pushes during doUndo/doRedo
+
+// -- Auto-snapshot trigger counter --
+var _savesSinceAutoSnap = 0;
+var _idleSnapTimer = null;  // fires after 20 minutes of inactivity
+
+ +
+

2. Undo / Redo

+

The core pattern is simple: snapshot the full state before each user-level mutation. Undo replaces the live state with the last snapshot and pushes the replaced state onto the redo stack.

+ +

Core stack logic

+
// Call _pushUndo() at the start of every mutation, before changing state.
+function _pushUndo() {
+  if (_undoInProgress) return;
+  _undoStack.push(JSON.stringify(S));
+  if (_undoStack.length > 50) _undoStack.shift();
+  _redoStack = [];
+  _persistUndoSession();
+  _updateUndoUI();
+}
+
+function doUndo() {
+  if (_undoStack.length === 0) return;
+  _redoStack.push(JSON.stringify(S));
+  if (_redoStack.length > 50) _redoStack.shift();
+  _undoInProgress = true;
+  S = JSON.parse(_undoStack.pop());
+  save();
+  render();
+  _undoInProgress = false;
+  _persistUndoSession();
+  _updateUndoUI();
+  notify('Undone' + ((_undoStack.length > 0) ? ' (' + _undoStack.length + ' more)' : ''));
+}
+
+function doRedo() {
+  if (_redoStack.length === 0) return;
+  _undoStack.push(JSON.stringify(S));
+  if (_undoStack.length > 50) _undoStack.shift();
+  _undoInProgress = true;
+  S = JSON.parse(_redoStack.pop());
+  save();
+  render();
+  _undoInProgress = false;
+  _persistUndoSession();
+  _updateUndoUI();
+  notify('Redone' + ((_redoStack.length > 0) ? ' (' + _redoStack.length + ' more)' : ''));
+}
+ +

Button state sync

+
function _updateUndoUI() {
+  var ub = document.getElementById('undo-btn');
+  var rb = document.getElementById('redo-btn');
+
+  if (ub) {
+    var ul = _undoStack.length;
+    ub.disabled = ul === 0;
+    ub.title = ul === 0
+      ? 'Nothing to undo'
+      : 'Undo (' + ul + ' step' + (ul !== 1 ? 's' : '') + ') - Ctrl+Z';
+  }
+
+  if (rb) {
+    var rl = _redoStack.length;
+    rb.disabled = rl === 0;
+    rb.title = rl === 0
+      ? 'Nothing to redo'
+      : 'Redo (' + rl + ' step' + (rl !== 1 ? 's' : '') + ') - Ctrl+Y';
+  }
+}
+ +

Session persistence

+

This mirrors the last 10 undo snapshots into sessionStorage. It can survive a page refresh, but clears when the browser tab is closed.

+
function _persistUndoSession() {
+  try {
+    var slice = _undoStack.slice(-10);
+    sessionStorage.setItem('fb4_undo', JSON.stringify(slice));
+  } catch (e) {}
+}
+
+function _restoreUndoSession() {
+  try {
+    var stored = sessionStorage.getItem('fb4_undo');
+    if (!stored) return;
+
+    var arr = JSON.parse(stored);
+    if (!Array.isArray(arr) || arr.length === 0) return;
+
+    _undoStack = arr;
+    _updateUndoUI();
+    notify('Undo history restored from last session (' + arr.length + ' step' + (arr.length !== 1 ? 's' : '') + ')');
+  } catch (e) {}
+}
+ +

Keyboard shortcuts

+
document.addEventListener('keydown', function(e) {
+  if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
+    e.preventDefault();
+    doUndo();
+  }
+
+  if ((e.ctrlKey || e.metaKey) && e.key === 'y') {
+    e.preventDefault();
+    doRedo();
+  }
+});
+ +

Mutation wiring pattern

+
function addItem(data) {
+  _pushUndo();
+  S.items.push(data);
+  save();
+  render();
+}
+
+function deleteItem(id) {
+  _pushUndo();
+  S.items = S.items.filter(function(x) { return x.id !== id; });
+  save();
+  render();
+}
+
+ +
+

3. Manual Backups

+

Manual snapshots are durable user-created save points stored in localStorage. They are intentionally separate from undo history.

+ +

Storage helpers

+
function getBaks() {
+  try {
+    return JSON.parse(localStorage.getItem('fb4_baks') || '[]');
+  } catch (e) {
+    return [];
+  }
+}
+
+function setBaks(a) {
+  localStorage.setItem('fb4_baks', JSON.stringify(a));
+}
+ +

Create, rename, delete, restore

+
function createBak(name) {
+  var a = getBaks();
+  var tc = S.cols.reduce(function(n, c) { return n + c.cards.length; }, 0);
+
+  a.unshift({
+    id: uid(),
+    name: name || 'Snapshot ' + new Date().toLocaleString(),
+    ts: Date.now(),
+    cards: tc,
+    data: JSON.stringify(S)
+  });
+
+  if (a.length > 25) a.splice(25);
+  setBaks(a);
+  notify('Snapshot saved.');
+}
+
+function renameBak(id) {
+  var baks = getBaks();
+  var b = baks.find(function(x) { return x.id === id; });
+  if (!b) return;
+
+  var orig = b.name;
+  var inp = document.createElement('input');
+  inp.value = orig;
+
+  inp.onblur = function() {
+    var v = inp.value.trim() || orig;
+    baks.forEach(function(x) {
+      if (x.id === id) x.name = v;
+    });
+    setBaks(baks);
+  };
+
+  inp.onkeydown = function(e) {
+    if (e.key === 'Enter') inp.blur();
+    else if (e.key === 'Escape') {
+      inp.value = orig;
+      inp.blur();
+    }
+  };
+
+  // Replace the name element with the input in your UI.
+}
+
+function deleteBak(id) {
+  setBaks(getBaks().filter(function(x) { return x.id !== id; }));
+}
+
+function restoreBak(id) {
+  var b = getBaks().find(function(x) { return x.id === id; });
+  if (!b || !confirm('Restore snapshot? Current board will be replaced.')) return;
+
+  _autoSnapBefore('Before: Restore snapshot - ' + new Date().toLocaleTimeString());
+  _undoStack = [];
+  _redoStack = [];
+  _persistUndoSession();
+  _updateUndoUI();
+
+  S = JSON.parse(b.data);
+  save();
+  render();
+  notify('Board restored.');
+}
+
+ +
+

4. Auto-Snapshots

+

Auto-snapshots protect users before risky operations and preserve useful recovery points without requiring manual action.

+
function getAutoBaks() {
+  try {
+    return JSON.parse(localStorage.getItem('fb4_auto') || '[]');
+  } catch (e) {
+    return [];
+  }
+}
+
+function setAutoBaks(a) {
+  try {
+    localStorage.setItem('fb4_auto', JSON.stringify(a));
+  } catch (e) {
+    console.warn('Storage full - auto-snapshot skipped.');
+  }
+}
+
+function _createAutoSnap(name, trigger) {
+  var autos = getAutoBaks();
+  autos.unshift({
+    id: uid(),
+    name: name,
+    ts: Date.now(),
+    trigger: trigger || 'auto',
+    data: JSON.stringify(S)
+  });
+
+  if (autos.length > 15) autos.splice(15);
+  setAutoBaks(autos);
+}
+
+function _autoSnapBefore(label) {
+  _createAutoSnap(label, 'destructive');
+}
+
+function _sessionStartSnap() {
+  var boardTs = S.lastModified || 0;
+  if (boardTs === 0) return;
+
+  var autos = getAutoBaks();
+  var lastAutoTs = autos.length > 0 ? autos[0].ts : 0;
+  if (boardTs <= lastAutoTs) return;
+
+  _createAutoSnap('Opened - ' + new Date().toLocaleString(), 'session');
+}
+
+function restoreAutoBak(id) {
+  var b = getAutoBaks().find(function(x) { return x.id === id; });
+  if (!b || !confirm('Restore this auto-snapshot? Current board will be replaced.')) return;
+
+  _autoSnapBefore('Before: Restore auto-snapshot - ' + new Date().toLocaleTimeString());
+  _undoStack = [];
+  _redoStack = [];
+  _persistUndoSession();
+  _updateUndoUI();
+
+  S = JSON.parse(b.data);
+  save();
+  render();
+  notify('Board restored from auto-snapshot.');
+}
+
+function deleteAutoBak(id) {
+  setAutoBaks(getAutoBaks().filter(function(x) { return x.id !== id; }));
+}
+
+ +
+

5. Export / Import

+

Export writes either the live board state or a named snapshot to a JSON file. Import validates the JSON, creates a protective auto-snapshot, then replaces the live state.

+
function exportBak(id) {
+  var data, name;
+
+  if (id) {
+    var b = getBaks().find(function(x) { return x.id === id; });
+    if (!b) return;
+    data = b.data;
+    name = b.name;
+  } else {
+    data = JSON.stringify(S);
+    name = 'flowboard_export';
+  }
+
+  var a = document.createElement('a');
+  a.href = URL.createObjectURL(new Blob([data], { type: 'application/json' }));
+  a.download = name.replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '_') + '_' + Date.now() + '.json';
+  a.click();
+}
+
+function importBak() {
+  var inp = document.createElement('input');
+  inp.type = 'file';
+  inp.accept = '.json';
+
+  inp.onchange = function(e) {
+    var f = e.target.files[0];
+    if (!f) return;
+
+    var r = new FileReader();
+    r.onload = function(ev) {
+      try {
+        var d = JSON.parse(ev.target.result);
+        if (!d.cols) throw 0;
+        if (!confirm('Import? Current board will be replaced.')) return;
+
+        _autoSnapBefore('Before: Import - ' + new Date().toLocaleTimeString());
+        _undoStack = [];
+        _redoStack = [];
+        _persistUndoSession();
+        _updateUndoUI();
+
+        S = d;
+        save();
+        render();
+        notify('Imported.');
+      } catch (e2) {
+        alert('Invalid file.');
+      }
+    };
+
+    r.readAsText(f);
+  };
+
+  inp.click();
+}
+
+ +
+

6. Archive

+

Archive is non-destructive. Items stay in the data model with flags and timestamps, while the active board render filters them out.

+ +

Mark as archived

+
function archiveCard(cid) {
+  _pushUndo();
+
+  for (var i = 0; i < S.cols.length; i++) {
+    var idx = S.cols[i].cards.findIndex(function(x) { return x.id === cid; });
+    if (idx >= 0) {
+      var card = S.cols[i].cards[idx];
+      card.archived = true;
+      card.archivedAt = Date.now();
+      card.archivedFromCol = S.cols[i].id;
+      break;
+    }
+  }
+
+  save();
+  render();
+  notify('Card archived.');
+}
+
+function archiveCol(cid) {
+  var col = S.cols.find(function(x) { return x.id === cid; });
+  if (!col) return;
+
+  _pushUndo();
+  col.archived = true;
+  col.archivedAt = Date.now();
+  save();
+  render();
+  notify('Column archived.');
+}
+ +

Restore from archive

+
function restoreCard(cid) {
+  _pushUndo();
+
+  for (var i = 0; i < S.cols.length; i++) {
+    var idx = S.cols[i].cards.findIndex(function(x) { return x.id === cid; });
+    if (idx >= 0) {
+      var card = S.cols[i].cards[idx];
+      delete card.archived;
+      delete card.archivedAt;
+      delete card.archivedFromCol;
+      break;
+    }
+  }
+
+  save();
+  render();
+  notify('Card restored.');
+}
+
+function restoreCol(cid) {
+  var col = S.cols.find(function(x) { return x.id === cid; });
+  if (!col) return;
+
+  _pushUndo();
+  delete col.archived;
+  delete col.archivedAt;
+
+  col.cards.forEach(function(card) {
+    delete card.archived;
+    delete card.archivedAt;
+    delete card.archivedFromCol;
+  });
+
+  save();
+  render();
+  notify('Column restored.');
+}
+ +

Permanent delete from archive

+
function deleteArchivedCard(cid) {
+  if (!confirm('Permanently delete this card?')) return;
+  _pushUndo();
+
+  for (var i = 0; i < S.cols.length; i++) {
+    var idx = S.cols[i].cards.findIndex(function(x) { return x.id === cid; });
+    if (idx >= 0) {
+      S.cols[i].cards.splice(idx, 1);
+      break;
+    }
+  }
+
+  save();
+  render();
+}
+
+function deleteArchivedCol(cid) {
+  if (!confirm('Permanently delete this column and all its cards?')) return;
+  _pushUndo();
+  S.cols = S.cols.filter(function(x) { return x.id !== cid; });
+  save();
+  render();
+}
+ +

Filter archived items out of the live board

+
var activeCols = S.cols.filter(function(c) { return !c.archived; });
+var activeCards = col.cards.filter(function(k) { return !k.archived; });
+
+ +
+

Key Design Patterns

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PatternHow it works
Undo granularityUse one _pushUndo() call per user action, not per keystroke.
Redo invalidationAny new action clears _redoStack, matching standard editor behavior.
Auto-snap triggersCreate snapshots at session start, after idle timeout, and before destructive deletes or full-state replacement.
Archive vs deleteArchive sets flags and preserves the item in state. Delete removes it from the array.
Import/restore clears undoReplacing the entire state makes prior undo steps unreliable, so both stacks are wiped.
+
When adapting these snippets to another app, rename the storage keys and replace schema-specific checks like d.cols with validation for that app's state model.
+
+
+
+
+ + diff --git a/flowboard_docs/index.html b/flowboard_docs/index.html new file mode 100644 index 0000000..6f064c3 --- /dev/null +++ b/flowboard_docs/index.html @@ -0,0 +1,134 @@ + + + + + + FlowBoard Docs + + + +
+
+

FlowBoard Documentation

+

Implementation notes and reusable code patterns for FlowBoard features. These docs focus on snippets that can be copied into similar browser-based apps.

+
+
+
+

Planning

+
+ +

Feature Groups

+

Grouped inventory of FlowBoard features and the recommended order for building reusable snippet pages.

+
+ +

Feature Patterns

+

Undo/redo, manual backups, auto-snapshots, import/export, and archive patterns.

+
+
+ +

Snippet Guides

+
+

State & Persistence

State shape, localStorage load/save, validation, and full-state replacement.

+

Recovery & History

Undo, redo, manual snapshots, auto-snapshots, and restore behavior.

+

Board Data Model

Groups, columns, cards, checklist items, IDs, selectors, and move helpers.

+

Editing Workflows

Add, rename, delete, duplicate, and checklist editing patterns.

+

Drag & Drop

Pointer drag, ghost elements, drop targets, and array movement.

+

Task Metadata

Priority, tags, due dates, color labels, and checklist progress.

+

Organization & Limits

Collapse states, WIP limits, priority sorting, and board stats.

+

Search & Filtering

Query matching, tag search, archive filtering, and checklist filters.

+

Theming & UI

CSS theme tokens, theme switching, palette helpers, and notifications.

+

Modals & Panels

Modal root, dirty-close guard, dropdowns, and click-outside panels.

+

Cloud Sync

Sync status, board keys, debounced writes, and safe remote write checks.

+

Exporting & Transfer

JSON download, import pipeline, filename cleanup, and print hooks.

+

Quality & Safety

Destructive action wrappers, undo clearing, storage checks, and safe import.

+
+ +
+ Folder purpose: flowboard_docs is only for FlowBoard documentation, so it stays separate from checklist docs and other tools in the repository. +
+
+ + diff --git a/flowboard_docs/snippets-board-data-model.html b/flowboard_docs/snippets-board-data-model.html new file mode 100644 index 0000000..6e58360 --- /dev/null +++ b/flowboard_docs/snippets-board-data-model.html @@ -0,0 +1,53 @@ + +Board Data Model Snippets

Board Data Model Snippets

A reusable Kanban state structure with groups, columns, cards, nested checklist items, and archived flags.

Back to Feature Groups

Schema Shape

var S = {
+  prefs: { theme: 'midnight', boardName: 'My Board' },
+  lastModified: 0,
+  groups: [
+    { id: 'g1', title: 'Work', pi: 0, collapsed: false, limit: 0 }
+  ],
+  cols: [
+    {
+      id: 'c1', gid: 'g1', title: 'To Do', pi: 0,
+      collapsed: false, sort: false, archived: false,
+      cards: [
+        { id: 'k1', title: 'Task', desc: '', pri: 1, color: null, tags: [], due: '', items: [], done: [] }
+      ]
+    }
+  ]
+};

Find Helpers

function findGroup(gid) {
+  return S.groups.find(function(g) { return g.id === gid; });
+}
+
+function findColumn(cid) {
+  return S.cols.find(function(c) { return c.id === cid; });
+}
+
+function findCard(cid) {
+  for (var i = 0; i < S.cols.length; i++) {
+    var card = S.cols[i].cards.find(function(k) { return k.id === cid; });
+    if (card) return { card: card, col: S.cols[i] };
+  }
+  return null;
+}

Active Selectors

function activeColumns() {
+  return S.cols.filter(function(c) { return !c.archived; });
+}
+
+function activeCards(col) {
+  return col.cards.filter(function(k) { return !k.archived; });
+}
+
+function boardCounts() {
+  var cols = activeColumns();
+  var cards = cols.reduce(function(n, c) { return n + activeCards(c).length; }, 0);
+  return { groups: S.groups.length, cols: cols.length, cards: cards };
+}

Move Card Between Columns

function moveCard(cardId, targetColId, targetIndex) {
+  var found = findCard(cardId);
+  var target = findColumn(targetColId);
+  if (!found || !target) return;
+
+  pushUndo();
+  found.col.cards = found.col.cards.filter(function(k) { return k.id !== cardId; });
+  target.cards.splice(targetIndex, 0, found.card);
+  saveState();
+  render();
+}
diff --git a/flowboard_docs/snippets-cloud-sync.html b/flowboard_docs/snippets-cloud-sync.html new file mode 100644 index 0000000..e9ce33b --- /dev/null +++ b/flowboard_docs/snippets-cloud-sync.html @@ -0,0 +1,32 @@ + +Cloud Sync Snippets

Cloud Sync Snippets

Optional Firebase-style sync patterns: status, board keys, debounced writes, and safety checks.

Back to Feature Groups

Sync Status

var sync = { status: 'local', key: '', ref: null, timer: null };
+
+function setSyncStatus(status) {
+  sync.status = status; // local | syncing | synced | error | offline
+  var el = document.getElementById('sync-status');
+  if (el) el.textContent = status;
+}

Board Key Helpers

function newBoardKey() {
+  var parts = [];
+  for (var i = 0; i < 5; i++) parts.push(Math.random().toString(16).slice(2, 6));
+  return parts.join('-');
+}
+
+function saveActiveKey(key) {
+  localStorage.setItem('fb4_active_key', key);
+  sync.key = key;
+}

Debounced Remote Write

function queueRemoteWrite() {
+  if (!sync.ref || sync.status === 'local') return;
+  clearTimeout(sync.timer);
+  sync.timer = setTimeout(function() {
+    if (!isSafeRemoteWrite(S)) return;
+    setSyncStatus('syncing');
+    sync.ref.set(S).then(function() { setSyncStatus('synced'); })
+      .catch(function() { setSyncStatus('error'); });
+  }, 600);
+}

Safe Remote Write Guard

function isSafeRemoteWrite(next) {
+  if (!next || !Array.isArray(next.cols)) return false;
+  var cardCount = next.cols.reduce(function(n, c) { return n + (c.cards || []).length; }, 0);
+  if (S.cols.length && next.cols.length === 0) return false;
+  if (boardCounts().cards > 5 && cardCount === 0) return false;
+  return true;
+}
diff --git a/flowboard_docs/snippets-drag-drop.html b/flowboard_docs/snippets-drag-drop.html new file mode 100644 index 0000000..1193928 --- /dev/null +++ b/flowboard_docs/snippets-drag-drop.html @@ -0,0 +1,40 @@ + +Drag & Drop Snippets

Drag & Drop Snippets

Pointer-based drag behavior for cards and columns, including ghosts, drop targets, and array movement.

Back to Feature Groups

Basic Pointer Drag Controller

var drag = null;
+
+function startDrag(e, data) {
+  drag = { data: data, startX: e.clientX, startY: e.clientY, active: false, ghost: null };
+  document.addEventListener('pointermove', onDragMove);
+  document.addEventListener('pointerup', endDrag, { once: true });
+}
+
+function onDragMove(e) {
+  if (!drag) return;
+  var dx = e.clientX - drag.startX;
+  var dy = e.clientY - drag.startY;
+  if (!drag.active && Math.hypot(dx, dy) > 5) activateDrag(e);
+  if (drag.active) positionGhost(e);
+}

Ghost Element

function activateDrag(e) {
+  drag.active = true;
+  drag.ghost = drag.data.el.cloneNode(true);
+  drag.ghost.style.position = 'fixed';
+  drag.ghost.style.pointerEvents = 'none';
+  drag.ghost.style.opacity = '0.88';
+  drag.ghost.style.transform = 'rotate(2deg) scale(1.04)';
+  document.body.appendChild(drag.ghost);
+  positionGhost(e);
+}
+
+function positionGhost(e) {
+  drag.ghost.style.left = e.clientX + 12 + 'px';
+  drag.ghost.style.top = e.clientY + 12 + 'px';
+}

Detect Drop Target

function getDropTarget(x, y) {
+  if (drag.ghost) drag.ghost.style.display = 'none';
+  var el = document.elementFromPoint(x, y);
+  if (drag.ghost) drag.ghost.style.display = '';
+  return el ? el.closest('[data-drop-target]') : null;
+}

Move Item in Arrays

function moveBetweenArrays(source, target, itemId, targetIndex) {
+  var idx = source.findIndex(function(x) { return x.id === itemId; });
+  if (idx < 0) return;
+  var item = source.splice(idx, 1)[0];
+  target.splice(targetIndex, 0, item);
+}
diff --git a/flowboard_docs/snippets-editing-workflows.html b/flowboard_docs/snippets-editing-workflows.html new file mode 100644 index 0000000..1f89067 --- /dev/null +++ b/flowboard_docs/snippets-editing-workflows.html @@ -0,0 +1,57 @@ + +Editing Workflow Snippets

Editing Workflow Snippets

Reusable patterns for adding, renaming, deleting, duplicating, and editing board data.

Back to Feature Groups

Add Group / Column / Card

function addGroup(title) {
+  pushUndo();
+  S.groups.push({ id: uid(), title: title || 'New Group', pi: 0, collapsed: false, limit: 0 });
+  saveState(); render();
+}
+
+function addColumn(title, gid) {
+  pushUndo();
+  S.cols.push({ id: uid(), gid: gid || null, title: title || 'New Column', pi: 0, collapsed: false, sort: false, cards: [] });
+  saveState(); render();
+}
+
+function addCard(colId, title) {
+  var col = findColumn(colId);
+  if (!col) return;
+  pushUndo();
+  col.cards.push({ id: uid(), title: title || 'New Card', desc: '', pri: 0, tags: [], items: [], done: [], due: '', color: null });
+  saveState(); render();
+}

Inline Rename Pattern

function startInlineRename(labelEl, currentValue, onSave) {
+  var input = document.createElement('input');
+  input.value = currentValue;
+  labelEl.replaceWith(input);
+  input.focus();
+  input.select();
+
+  input.onblur = function() {
+    var next = input.value.trim() || currentValue;
+    onSave(next);
+  };
+
+  input.onkeydown = function(e) {
+    if (e.key === 'Enter') input.blur();
+    if (e.key === 'Escape') {
+      input.value = currentValue;
+      input.blur();
+    }
+  };
+}

Delete with Snapshot

function deleteColumn(colId) {
+  if (!confirm('Delete this column and all cards?')) return;
+  createAutoSnapshot('Before deleting column', 'destructive');
+  pushUndo();
+  S.cols = S.cols.filter(function(c) { return c.id !== colId; });
+  saveState(); render();
+}

Checklist Item Operations

function addChecklistItem(card, text) {
+  pushUndo();
+  card.items.unshift(text);
+  saveState(); render();
+}
+
+function toggleChecklistItem(card, text) {
+  pushUndo();
+  var i = card.done.indexOf(text);
+  if (i >= 0) card.done.splice(i, 1);
+  else card.done.push(text);
+  saveState(); render();
+}
diff --git a/flowboard_docs/snippets-exporting-transfer.html b/flowboard_docs/snippets-exporting-transfer.html new file mode 100644 index 0000000..92faf78 --- /dev/null +++ b/flowboard_docs/snippets-exporting-transfer.html @@ -0,0 +1,37 @@ + +Exporting & Transfer Snippets

Exporting & Transfer Snippets

JSON download, import pipeline, filename cleanup, and print/export helpers.

Back to Feature Groups

Download JSON

function safeFileName(name) {
+  return (name || 'export').replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '_');
+}
+
+function downloadJson(data, name) {
+  var blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
+  var a = document.createElement('a');
+  a.href = URL.createObjectURL(blob);
+  a.download = safeFileName(name) + '_' + Date.now() + '.json';
+  a.click();
+  URL.revokeObjectURL(a.href);
+}

Export Current State

function exportCurrentBoard() {
+  downloadJson(S, 'flowboard_export');
+}

Import JSON File

function importJsonFile() {
+  var input = document.createElement('input');
+  input.type = 'file';
+  input.accept = '.json,application/json';
+  input.onchange = function(e) {
+    var file = e.target.files[0];
+    if (!file) return;
+    var reader = new FileReader();
+    reader.onload = function(ev) {
+      var next = JSON.parse(ev.target.result);
+      if (!validateState(next)) return alert('Invalid file.');
+      if (!confirm('Import? Current board will be replaced.')) return;
+      createAutoSnapshot('Before import', 'destructive');
+      replaceState(next);
+    };
+    reader.readAsText(file);
+  };
+  input.click();
+}

Print Export Hook

function printCurrentView() {
+  document.body.classList.add('print-mode');
+  window.print();
+  setTimeout(function() { document.body.classList.remove('print-mode'); }, 0);
+}
diff --git a/flowboard_docs/snippets-modals-panels.html b/flowboard_docs/snippets-modals-panels.html new file mode 100644 index 0000000..f13de07 --- /dev/null +++ b/flowboard_docs/snippets-modals-panels.html @@ -0,0 +1,38 @@ + +Modals & Panels Snippets

Modals & Panels Snippets

Generic overlay, floating panel, dirty-close, and dropdown patterns for single-page tools.

Back to Feature Groups

Generic Modal Root

function setModal(html, width) {
+  var root = document.getElementById('modal-root');
+  if (!root) {
+    root = document.createElement('div');
+    root.id = 'modal-root';
+    document.body.appendChild(root);
+  }
+
+  root.innerHTML = '
'; +} + +function closeModal() { + var root = document.getElementById('modal-root'); + if (root) root.innerHTML = ''; +}

Dirty Close Guard

var modalDirty = false;
+
+function guardedCloseModal() {
+  if (modalDirty && !confirm('Discard unsaved changes?')) return;
+  modalDirty = false;
+  closeModal();
+}

Click Outside to Close Panel

function openPanel(panel, anchor) {
+  panel.classList.add('open');
+
+  setTimeout(function() {
+    document.addEventListener('pointerdown', function handler(e) {
+      if (!panel.contains(e.target) && !anchor.contains(e.target)) {
+        panel.classList.remove('open');
+        document.removeEventListener('pointerdown', handler);
+      }
+    });
+  }, 0);
+}

Dropdown Select Pattern

function selectFromDropdown(value, onSelect) {
+  pushUndo();
+  onSelect(value);
+  saveState();
+  render();
+}
diff --git a/flowboard_docs/snippets-organization-limits.html b/flowboard_docs/snippets-organization-limits.html new file mode 100644 index 0000000..297217d --- /dev/null +++ b/flowboard_docs/snippets-organization-limits.html @@ -0,0 +1,30 @@ + +Organization & Limits Snippets

Organization & Limits Snippets

Patterns for collapse states, WIP limits, active selectors, sorting, and board statistics.

Back to Feature Groups

Collapse Toggles

function toggleGroup(gid) {
+  var group = findGroup(gid);
+  if (!group) return;
+  pushUndo();
+  group.collapsed = !group.collapsed;
+  saveState(); render();
+}
+
+function toggleColumn(cid) {
+  var col = findColumn(cid);
+  if (!col) return;
+  pushUndo();
+  col.collapsed = !col.collapsed;
+  saveState(); render();
+}

WIP Limit Height

var CARD_SLOT_H = 86;
+
+function columnBodyHeight(group, cardCount) {
+  if (!group || !group.limit) return '';
+  return Math.max(1, group.limit) * CARD_SLOT_H + 'px';
+}

Priority Sort Toggle

function visibleCards(col) {
+  var cards = activeCards(col).slice();
+  if (col.sort) {
+    cards.sort(function(a, b) { return (b.pri || 0) - (a.pri || 0); });
+  }
+  return cards;
+}

Stats Chip

function statsText() {
+  var counts = boardCounts();
+  return counts.groups + ' groups · ' + counts.cols + ' cols · ' + counts.cards + ' cards';
+}
diff --git a/flowboard_docs/snippets-quality-safety.html b/flowboard_docs/snippets-quality-safety.html new file mode 100644 index 0000000..201a258 --- /dev/null +++ b/flowboard_docs/snippets-quality-safety.html @@ -0,0 +1,32 @@ + +Quality & Safety Snippets

Quality & Safety Snippets

Defensive wrappers and checks that reduce accidental data loss and make browser tools more resilient.

Back to Feature Groups

Destructive Action Wrapper

function runDestructive(label, confirmText, fn) {
+  if (confirmText && !confirm(confirmText)) return false;
+  createAutoSnapshot(label, 'destructive');
+  pushUndo();
+  fn();
+  saveState();
+  render();
+  return true;
+}

Clear Undo After Full Replace

function clearUndoRedo() {
+  _undoStack = [];
+  _redoStack = [];
+  persistUndoSession();
+  updateUndoButtons();
+}

Storage Quota Check

function estimateStorageBytes() {
+  var total = 0;
+  for (var i = 0; i < localStorage.length; i++) {
+    var key = localStorage.key(i);
+    total += key.length + (localStorage.getItem(key) || '').length;
+  }
+  return total * 2;
+}
+
+function warnIfStorageHigh() {
+  if (estimateStorageBytes() > 4 * 1024 * 1024) notify('Storage is getting full. Export a backup soon.');
+}

Safe Import Pipeline

function safeImportState(next) {
+  if (!validateState(next)) return alert('Invalid state file.');
+  runDestructive('Before full-state import', 'Replace current board?', function() {
+    clearUndoRedo();
+    S = next;
+  });
+}
diff --git a/flowboard_docs/snippets-recovery-history.html b/flowboard_docs/snippets-recovery-history.html new file mode 100644 index 0000000..6b9c96a --- /dev/null +++ b/flowboard_docs/snippets-recovery-history.html @@ -0,0 +1,71 @@ + +Recovery & History Snippets

Recovery & History Snippets

Undo, redo, manual snapshots, and auto-snapshots for preventing data loss in single-page browser apps.

Back to Feature Groups
Depends on: state replacement and persistence helpers from the State & Persistence guide.

Undo / Redo Stack

var _undoStack = [];
+var _redoStack = [];
+var _undoInProgress = false;
+
+function pushUndo() {
+  if (_undoInProgress) return;
+  _undoStack.push(JSON.stringify(S));
+  if (_undoStack.length > 50) _undoStack.shift();
+  _redoStack = [];
+  persistUndoSession();
+  updateUndoButtons();
+}
+
+function doUndo() {
+  if (!_undoStack.length) return;
+  _redoStack.push(JSON.stringify(S));
+  _undoInProgress = true;
+  replaceState(JSON.parse(_undoStack.pop()), { touch: false });
+  _undoInProgress = false;
+  persistUndoSession();
+  updateUndoButtons();
+}
+
+function doRedo() {
+  if (!_redoStack.length) return;
+  _undoStack.push(JSON.stringify(S));
+  _undoInProgress = true;
+  replaceState(JSON.parse(_redoStack.pop()), { touch: false });
+  _undoInProgress = false;
+  persistUndoSession();
+  updateUndoButtons();
+}

Session Undo Restore

function persistUndoSession() {
+  try { sessionStorage.setItem('fb4_undo', JSON.stringify(_undoStack.slice(-10))); }
+  catch (e) {}
+}
+
+function restoreUndoSession() {
+  try {
+    var arr = JSON.parse(sessionStorage.getItem('fb4_undo') || '[]');
+    if (Array.isArray(arr)) _undoStack = arr;
+    updateUndoButtons();
+  } catch (e) {}
+}

Manual Snapshots

function getSnapshots() {
+  try { return JSON.parse(localStorage.getItem('fb4_baks') || '[]'); }
+  catch (e) { return []; }
+}
+
+function setSnapshots(items) {
+  localStorage.setItem('fb4_baks', JSON.stringify(items));
+}
+
+function createSnapshot(name) {
+  var items = getSnapshots();
+  items.unshift({ id: uid(), name: name || 'Snapshot', ts: Date.now(), data: JSON.stringify(S) });
+  if (items.length > 25) items.splice(25);
+  setSnapshots(items);
+}

Auto-Snapshot Before Risky Actions

function createAutoSnapshot(label, trigger) {
+  var items = JSON.parse(localStorage.getItem('fb4_auto') || '[]');
+  items.unshift({ id: uid(), name: label, trigger: trigger || 'auto', ts: Date.now(), data: JSON.stringify(S) });
+  if (items.length > 15) items.splice(15);
+  localStorage.setItem('fb4_auto', JSON.stringify(items));
+}
+
+function withDestructiveSnapshot(label, fn) {
+  createAutoSnapshot(label, 'destructive');
+  pushUndo();
+  fn();
+  saveState();
+  render();
+}
diff --git a/flowboard_docs/snippets-search-filtering.html b/flowboard_docs/snippets-search-filtering.html new file mode 100644 index 0000000..285c6a0 --- /dev/null +++ b/flowboard_docs/snippets-search-filtering.html @@ -0,0 +1,26 @@ + +Search & Filtering Snippets

Search & Filtering Snippets

Small helpers for filtering cards by title, tags, archive state, and checklist status.

Back to Feature Groups

Filter State

var filters = {
+  query: '',
+  showArchived: false,
+  checklistMode: 'all' // all | remaining | done
+};

Card Search Matcher

function cardMatchesQuery(card, query) {
+  query = (query || '').trim().toLowerCase();
+  if (!query) return true;
+
+  var title = (card.title || '').toLowerCase();
+  var tags = (card.tags || []).join(' ').toLowerCase();
+  return title.indexOf(query) >= 0 || tags.indexOf(query) >= 0;
+}

Apply Card Filters

function filteredCards(col) {
+  return col.cards.filter(function(card) {
+    if (!filters.showArchived && card.archived) return false;
+    if (!cardMatchesQuery(card, filters.query)) return false;
+    return true;
+  });
+}

Checklist Status Filter

function checklistItemsForMode(card, mode) {
+  return card.items.filter(function(item) {
+    var done = card.done.indexOf(item) >= 0;
+    if (mode === 'done') return done;
+    if (mode === 'remaining') return !done;
+    return true;
+  });
+}
diff --git a/flowboard_docs/snippets-state-persistence.html b/flowboard_docs/snippets-state-persistence.html new file mode 100644 index 0000000..3cc0ef1 --- /dev/null +++ b/flowboard_docs/snippets-state-persistence.html @@ -0,0 +1,76 @@ + + + + + + State & Persistence Snippets + + + +

State & Persistence Snippets

Foundation helpers for browser apps that store their full state locally and replace that state during imports, restores, or sync events.

+
+Back to Feature Groups +
Use first: these helpers should exist before undo, snapshots, import/export, archive, or cloud sync. Those systems all depend on predictable state load, save, validation, and replacement.
+

Storage Keys and Default State

var STORE_KEY = 'fb4';
+
+function uid() {
+  return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
+}
+
+function defaultState() {
+  return {
+    prefs: { theme: 'midnight', modalW: 510, modalH: null, boardName: 'My Board' },
+    lastModified: 0,
+    groups: [],
+    cols: []
+  };
+}
+

Validate State Shape

function validateState(data) {
+  if (!data || typeof data !== 'object') return false;
+  if (!Array.isArray(data.groups)) return false;
+  if (!Array.isArray(data.cols)) return false;
+  if (!data.prefs || typeof data.prefs !== 'object') data.prefs = {};
+
+  data.cols.forEach(function(col) {
+    if (!Array.isArray(col.cards)) col.cards = [];
+  });
+
+  return true;
+}
+

Load and Save

var S = defaultState();
+
+function loadState() {
+  try {
+    var raw = localStorage.getItem(STORE_KEY);
+    if (!raw) {
+      S = defaultState();
+      return;
+    }
+
+    var parsed = JSON.parse(raw);
+    S = validateState(parsed) ? parsed : defaultState();
+  } catch (e) {
+    console.warn('State failed to load. Starting from defaults.', e);
+    S = defaultState();
+  }
+}
+
+function saveState() {
+  S.lastModified = Date.now();
+  localStorage.setItem(STORE_KEY, JSON.stringify(S));
+}
+

Replace State Safely

function replaceState(nextState, options) {
+  options = options || {};
+  if (!validateState(nextState)) throw new Error('Invalid state object.');
+
+  S = nextState;
+  if (options.touch !== false) S.lastModified = Date.now();
+
+  localStorage.setItem(STORE_KEY, JSON.stringify(S));
+  render();
+}

Use replaceState() for imports, snapshot restores, undo/redo, and remote sync so all full-state replacement paths behave consistently.

+
+ + diff --git a/flowboard_docs/snippets-task-metadata.html b/flowboard_docs/snippets-task-metadata.html new file mode 100644 index 0000000..68df487 --- /dev/null +++ b/flowboard_docs/snippets-task-metadata.html @@ -0,0 +1,42 @@ + +Task Metadata Snippets

Task Metadata Snippets

Reusable helpers for priority, tags, due dates, color labels, and checklist progress.

Back to Feature Groups

Priority Labels

var PRIORITIES = [
+  { label: 'Low', className: 'pri-low' },
+  { label: 'Med', className: 'pri-med' },
+  { label: 'High', className: 'pri-high' }
+];
+
+function priorityInfo(value) {
+  return PRIORITIES[value] || PRIORITIES[0];
+}

Tag Add / Remove

function addTag(card, tag) {
+  tag = tag.trim();
+  if (!tag || card.tags.indexOf(tag) >= 0) return;
+  pushUndo();
+  card.tags.push(tag);
+  saveState(); render();
+}
+
+function removeTag(card, tag) {
+  pushUndo();
+  card.tags = card.tags.filter(function(x) { return x !== tag; });
+  saveState(); render();
+}

Due Date Classifier

function dueInfo(isoDate) {
+  if (!isoDate) return { label: '', className: '' };
+  var today = new Date();
+  today.setHours(0, 0, 0, 0);
+  var due = new Date(isoDate + 'T00:00:00');
+  var diff = Math.round((due - today) / 86400000);
+
+  if (diff < 0) return { label: Math.abs(diff) + 'd overdue', className: 'due-over' };
+  if (diff === 0) return { label: 'Today', className: 'due-soon' };
+  if (diff === 1) return { label: 'Tomorrow', className: 'due-soon' };
+  if (diff <= 2) return { label: diff + 'd', className: 'due-soon' };
+  return { label: due.toLocaleDateString(), className: 'due-ok' };
+}

Checklist Progress

function checklistProgress(card) {
+  var total = card.items.length;
+  var done = card.done.length;
+  return {
+    done: done,
+    total: total,
+    pct: total ? Math.round((done / total) * 100) : 0
+  };
+}
diff --git a/flowboard_docs/snippets-theming-ui.html b/flowboard_docs/snippets-theming-ui.html new file mode 100644 index 0000000..3114917 --- /dev/null +++ b/flowboard_docs/snippets-theming-ui.html @@ -0,0 +1,41 @@ + +Theming & UI Snippets

Theming & UI System Snippets

CSS token themes, theme switching, color pickers, toasts, and small UI primitives.

Back to Feature Groups

CSS Theme Tokens

:root {
+  --bg: #060d18;
+  --panel: #1a2538;
+  --text: #e2e8f0;
+  --muted: #94a3b8;
+  --border: rgba(255,255,255,.1);
+}
+
+[data-theme="pearl"] {
+  --bg: #f5f5f7;
+  --panel: #ffffff;
+  --text: #1d1d1f;
+  --muted: #6e6e73;
+  --border: rgba(0,0,0,.12);
+}

Apply and Save Theme

function applyTheme(id, persist) {
+  document.documentElement.dataset.theme = id;
+  if (persist) {
+    S.prefs.theme = id;
+    saveState();
+  }
+}
+
+function loadTheme() {
+  applyTheme((S.prefs && S.prefs.theme) || 'midnight', false);
+}

Palette Picker

var PAL = ['#2563eb', '#7c3aed', '#e11d48', '#059669', '#d97706'];
+
+function setPaletteIndex(item, pi) {
+  pushUndo();
+  item.pi = pi;
+  saveState();
+  render();
+}

Toast Notification

function notify(message) {
+  var el = document.getElementById('notif') || document.createElement('div');
+  el.id = 'notif';
+  el.textContent = message;
+  el.className = 'toast show';
+  document.body.appendChild(el);
+  clearTimeout(notify.timer);
+  notify.timer = setTimeout(function() { el.classList.remove('show'); }, 2600);
+}
diff --git a/mockups/topbar/option-a.html b/mockups/topbar/option-a.html new file mode 100644 index 0000000..3a45dd6 --- /dev/null +++ b/mockups/topbar/option-a.html @@ -0,0 +1,68 @@ + + + + +Topbar Mockup — Option A: Reordered + + + +
+ +
+ + +
+
3 groups · 9 cols · 42 cards
+
+ + + 💾 Local + + + + +
+
+ Board content here… +
+
+ Option A — Reordered
+ Smallest possible change. Undo/Redo move to the top-left as a segmented + pair next to the logo (standard convention). "🎨 Theme" becomes + "⚙ Settings" and absorbs the Archive section (see settings-panel.html). + "+ Group" / "+ Column" and the standalone "📦 Archive" button are removed — + everything else keeps its current position. +
+ + diff --git a/mockups/topbar/option-b.html b/mockups/topbar/option-b.html new file mode 100644 index 0000000..527891d --- /dev/null +++ b/mockups/topbar/option-b.html @@ -0,0 +1,73 @@ + + + + +Topbar Mockup — Option B: Grouped Clusters + + + +
+ +
+
+ + +
+
+
3 groups · 9 cols · 42 cards
+
+ + +
+ 💾 Local + +
+ + + +
+
+ Board content here… +
+
+ Option B — Grouped Clusters
+ Same as Option A, but related controls are visually clustered with thin + separators: [Undo/Redo] · stats | [Search + Filter] | [Sync status + Sync] | + [Backups, Report, Settings]. Makes the bar read as logical sections instead + of one long row, without changing any functionality. +
+ + diff --git a/mockups/topbar/option-c.html b/mockups/topbar/option-c.html new file mode 100644 index 0000000..caf2d95 --- /dev/null +++ b/mockups/topbar/option-c.html @@ -0,0 +1,84 @@ + + + + +Topbar Mockup — Option C: Compact Icons + Overflow + + + +
+ +
+ + +
+
3 groups · 9 cols · 42 cards
+
+ + + 💾 Local + + +
+ +
+ + +
+
+
+
+ Board content here… +
+
+ Option C — Compact Icons + Overflow
+ Most aggressive declutter: Undo/Redo, Filter, Sync and Settings become + icon-only buttons (tooltips on hover) at top-left/right. Less-frequent + actions (Backups, Report) move into a "⋯ More" menu. Frees the most + horizontal space — best if the bar feels cramped on smaller screens, but + hides labels until hover. +
+ + + diff --git a/mockups/topbar/settings-panel.html b/mockups/topbar/settings-panel.html new file mode 100644 index 0000000..cec1b1c --- /dev/null +++ b/mockups/topbar/settings-panel.html @@ -0,0 +1,61 @@ + + + + +Settings Panel Mockup — Archive section + + + +
+
Board Theme
+
+
+
+
+ +
Date Format
+
+ + + +
+ +
Archived Columns
+
+ 📦 4 archived columns + +
+
+
+ Settings panel addition
+ The "🎨 Theme" button becomes "⚙ Settings" and gains a new section showing + a count of archived columns plus a button that opens the existing Archive + modal — same modal, just relocated out of the main topbar. +
+ + diff --git a/roadway-plans-checklist.html b/roadway-plans-checklist.html deleted file mode 100644 index 95f955b..0000000 --- a/roadway-plans-checklist.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - -Roadway Plans Checklist – Section 13 - - - - - -
-
-
Plan Presentation Guide · Rev 3.0 · Section 13
-

Mainline Roadway, Crossroad, Side Street, Frontage Road and Ramp Plan Drawings

-
-
-
0/0
-
Complete
-
-
-
- -
-
-
- - - Local autosave active -
- -
- -
✓ All items complete!
- -
-
- - - - diff --git a/row-checklist.html b/row-checklist.html deleted file mode 100644 index 39c84ee..0000000 --- a/row-checklist.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - -Right of Way Checklist - - - - - -
-
-
Plan Presentation Guide · Rev 3.0
-

Right of Way Checklist

-
-
-
0/0
-
Complete
-
-
-
- -
-
-
- - - Local autosave active -
- -
- -
✓ All items complete!
- -
-
- - - - diff --git a/section00-template.html b/section00-template.html new file mode 100644 index 0000000..57a5dd4 --- /dev/null +++ b/section00-template.html @@ -0,0 +1,855 @@ + + + + + +Section XX — Checklist + + + + + + +
Generating image...
+ +
+ +
+
+
Plan Presentation Guide · Section XX
+
+ + + Checklist Title +
+
+
+
+
0/0
+
Complete
+
+
+
+ +
+
+ + + +
+
+ + + + +
+
+ +
+
+
+ +
+
All items complete - great work!
+
+
No items match this filter.
+
+ + + + diff --git a/utility-plans-checklist.html b/utility-plans-checklist.html deleted file mode 100644 index 0eae15b..0000000 --- a/utility-plans-checklist.html +++ /dev/null @@ -1,556 +0,0 @@ - - - - - -Section 24 – Utility Plans Checklist - - - - - -
-
-
Plan Presentation Guide · Section 24
-

Utility Plans Checklist

-
-
-
0/0
-
Complete
-
-
-
- -
-
-
- - - Local autosave active -
- -
- -
✓ All items complete!
- -
-
- - - -