{"id":47696576,"url":"https://github.com/layogtima/ferment","last_synced_at":"2026-04-02T16:28:43.992Z","repository":{"id":344159807,"uuid":"1180677459","full_name":"layogtima/ferment","owner":"layogtima","description":"Browse recipes, read wiki articles, track batches, manage your pantry and equipment, and master the ancient art of fermentation!","archived":false,"fork":false,"pushed_at":"2026-03-24T04:20:48.000Z","size":1873,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-24T22:08:56.576Z","etag":null,"topics":["brine","dill","fermenting","garlic","ginger","kimchi","onion","pickles","salt"],"latest_commit_sha":null,"homepage":"http://ferment.layogtima.com/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/layogtima.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-13T09:42:30.000Z","updated_at":"2026-03-24T04:20:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/layogtima/ferment","commit_stats":null,"previous_names":["layogtima/ferment"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/layogtima/ferment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/layogtima%2Fferment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/layogtima%2Fferment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/layogtima%2Fferment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/layogtima%2Fferment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/layogtima","download_url":"https://codeload.github.com/layogtima/ferment/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/layogtima%2Fferment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31309929,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brine","dill","fermenting","garlic","ginger","kimchi","onion","pickles","salt"],"created_at":"2026-04-02T16:28:43.220Z","updated_at":"2026-04-02T16:28:43.965Z","avatar_url":"https://github.com/layogtima.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FERMENT\n\n**A cultural guide to lactic acid fermentation from around the world.**\n\nBrowse 30 recipes, read 23 wiki articles, track batches, manage your pantry and equipment, and master the ancient art of fermentation - all in a single-page, offline-capable web app.\n\n---\n\n## Tech Stack\n\n| Layer | Choice | Notes |\n|-------|--------|-------|\n| **Framework** | Vue 3 (CDN) | `vue.global.prod.js` - no build step |\n| **CSS** | Tailwind CSS (CDN) | Config inline in `\u003cscript\u003e` block in `index.html` |\n| **Fonts** | Instrument Serif, DM Sans, JetBrains Mono | Google Fonts |\n| **Storage** | localStorage | Persisted via `FermentStore` helper |\n| **PWA** | Service Worker + manifest.json | Offline support |\n\n## Project Structure\n\n```\nferment/\n├── index.html                  # App shell, Tailwind config, Vue mount, OG meta tags\n├── css/\n│   └── app.css                 # Custom styles (vars, animations, wiki, editors)\n├── js/\n│   ├── app.js                  # Vue 3 app init, state, routing, history, OG meta\n│   ├── store.js                # localStorage persistence + import/export\n│   ├── recipes.js              # Recipe loader (JSON manifest + fallback)\n│   ├── wiki.js                 # Wiki article loader (JSON manifest)\n│   ├── utils/\n│   │   ├── formatting.js       # FermentFormat - units, scaling, conversion\n│   │   ├── search.js           # FermentSearch - full-text fuzzy search\n│   │   └── matching.js         # FermentMatching - pantry-to-recipe matching\n│   └── components/\n│       ├── SearchBar.js        # Search input component\n│       ├── FilterPanel.js      # Multi-facet filter UI\n│       ├── RecipeCard.js       # Card / list / table row for a recipe\n│       ├── RecipePage.js       # Full-page recipe view with inline editing\n│       ├── BrowseView.js       # Recipe browsing grid + filters + sort\n│       ├── WikiView.js         # Wiki article list, search, collapsible tag filter\n│       ├── WikiArticle.js      # Rich wiki article renderer + inline editing\n│       ├── PantryManager.js    # Pantry + equipment inventory CRUD\n│       ├── JournalManager.js   # Batch tracking / journal entries\n│       ├── BrineCalculator.js  # Salt / brine ratio calculator\n│       ├── BatchScaler.js      # Scale any recipe up/down\n│       ├── TimerManager.js     # Timer management\n│       ├── ToolsView.js        # Tools menu (calculator, converter, pH, calendar)\n│       ├── SettingsModal.js    # App settings (links to standalone changelog page)\n│       ├── ChangelogView.js   # Standalone changelog with Antigravity-style release cards\n│       ├── OnboardingModal.js  # First-run onboarding wizard\n│       ├── WelcomePage.js      # Welcome / intro page\n│       ├── InlineEditor.js     # Edit framework + FermentEdits store\n│       └── editors/            # Field-specific inline editors\n│           ├── TextEditor.js\n│           ├── ListEditor.js\n│           ├── MediaPicker.js\n│           ├── TagEditor.js\n│           └── CitationEditor.js\n├── data/\n│   ├── recipes/\n│   │   ├── manifest.json       # Recipe file index\n│   │   ├── tier1-beginner.js   # Fallback recipe data\n│   │   └── individual/         # 30 individual recipe JSON files\n│   └── wiki/\n│       ├── manifest.json       # Wiki article index (23 articles)\n│       └── *.json              # Individual wiki article files\n├── assets/\n│   └── icons/                  # Favicon, PWA icons\n├── manifest.json               # PWA manifest\n└── sw.js                       # Service worker (cache-first)\n```\n\n## Deployment\n\n### Cache Busting\n\nAll local JS files include a `?v=YYYYMMDD` query string (e.g. `?v=20260310`). On each deployment, bump this version string across all `\u003cscript src=\"...?v=...\"\u003e` tags in `index.html` and update the `CACHE_NAME` in `sw.js` to force browsers and the service worker to reload fresh assets.\n\n## Features\n\n### Recipes\n- **30 recipes** from cultures worldwide with rich metadata\n- Card, list, and table view modes with hero images\n- Shelf life display on cards and recipe detail pages\n- Difficulty tiers, fermentation time, ingredient counts\n- Step-by-step instructions with 3 tips per step\n- Cultural context, variations, and dehydrator integration\n\n### Wiki\n- **23 articles** covering fermentation science, safety, equipment, and techniques\n- Rich content: tables, callouts, images, citations, cross-links\n- Collapsible tag filter for easy browsing\n- Quick glossary search across all articles\n\n### Pantry \u0026 Equipment\n- Full inventory management with categories (produce, spices, salt, cultures, equipment)\n- Equipment items with product links, images, and descriptions\n- Quick-add equipment suggestions (jars, airlocks, weights, scales)\n- Recipe matching: see what you can make with what you have\n\n### Sharing \u0026 Meta\n- URL-based sharing via hash routing (`#/recipe/slug`, `#/wiki/slug`)\n- Dynamic Open Graph and Twitter Card meta tags\n- SVG-based OG images for wiki articles\n- Recipe hero images as OG images\n\n### Tools\n- Brine Calculator, Batch Scaler, Timers, Unit Converter, pH Reference, Seasonal Calendar\n\n## Architecture\n\n### Routing \u0026 History\n\nNo router library. State-driven routing using reactive refs:\n\n- **`currentRoute`**: `'home'` | `'recipe'` | `'wiki-article'` | `'welcome'` | `'changelog'`\n- **`currentTab`**: `'browse'` | `'wiki'` | `'pantry'` | `'journal'` | `'tools'`\n\nBrowser history managed with `history.pushState()` / `popstate`. Hash URLs (`#/recipe/slug`, `#/wiki/slug`, `#/changelog`) are restored on page load for sharing. Back button uses `history.back()` for clean navigation.\n\n### Data Flow\n\n```\nindex.html (template + OG meta tags)\n  └─ app.js (setup, state, actions, meta updates)\n       ├─ FermentStore (localStorage read/write)\n       ├─ FermentRecipes (recipe loader - JSON manifest)\n       ├─ FermentWiki (wiki loader - JSON manifest)\n       ├─ FermentSearch (search index)\n       ├─ FermentMatching (pantry matching)\n       └─ FermentEdits (inline edit overlay store)\n```\n\nState mutations flow **down** via props, events flow **up** via `$emit`. The root `app.js` owns all state and persists to localStorage on every meaningful change.\n\n### Error Handling\n\nFERMENT uses a **three-layer error boundary** pattern to guarantee the app never white-screens:\n\n| Layer | Mechanism | Scope |\n|-------|-----------|-------|\n| **Component** | `errorCaptured(err, _vm, info)` hook | Catches errors in child components, sets a local error state, renders a dismissible error banner |\n| **Global** | `app.config.errorHandler` | Catch-all for anything that escapes component boundaries |\n| **Async** | `try/catch/finally` in `onMounted` | Ensures `ready.value = true` always fires, even if data loading fails |\n\n**Rules for new components:**\n\n1. Every component that renders dynamic data or has children **must** include an `errorCaptured` hook\n2. Add a corresponding `componentError: null` data property\n3. Show an error banner with a dismiss button when the error is set: `\u003cdiv v-if=\"componentError\"\u003e...\u003c/div\u003e`\n4. Wrap the main content in `\u003ctemplate v-if=\"!componentError\"\u003e` so a crashed section degrades gracefully\n5. Return `false` from `errorCaptured` to stop propagation\n\n**Reference implementation:** See `BrowseView.js` for the canonical pattern.\n\n**Utility functions** (`FermentFormat`, etc.) must guard against invalid inputs - especially `new Date()` which silently returns Invalid Date rather than throwing. Always check `isNaN(d.getTime())` before calling date methods.\n\n### Inline Editing\n\nRecipes and wiki articles support inline editing when enabled in Settings. Edits are stored as localStorage overlays (via `FermentEdits`), merged at render time with the original JSON data. **Disabled by default** - toggle in Settings \u003e Enable Editing.\n\n## Running Locally\n\nStatic files - serve with any HTTP server:\n\n```bash\npython3 -m http.server 8000\n```\n\nOpen `http://localhost:8000` in your browser.\n\n## Adding Content\n\n### Recipes\n\n1. Create a JSON file in `data/recipes/individual/` following the schema of existing recipes\n2. Add the entry to `data/recipes/manifest.json`\n3. Update `sw.js` cache list\n\n### Wiki Articles\n\n1. Create a JSON file in `data/wiki/` with sections, citations, and cross-links\n2. Add the entry to `data/wiki/manifest.json`\n3. Update `sw.js` cache list\n\n---\n\n## Contributing\n\nThe README and `ChangelogView.js` are the **source of truth** for this project's roadmap and history. Every meaningful contribution - whether from a human or an AI agent - must keep both up to date.\n\n### For Humans\n\n1. **Fork** the repository and create a branch: `git checkout -b feat/your-feature`\n2. Make your changes following the existing patterns (Vue 3 CDN component style, Tailwind classes, no build step)\n3. **Update the changelog** - add a new entry or items to the latest entry in `js/components/ChangelogView.js`:\n   - `feature` - new capability\n   - `enhancement` - improvement to existing feature\n   - `fix` - bug fix\n4. **Update this README** if you changed the architecture, routing, or project structure\n5. Open a pull request with a clear title and description\n\n**PR checklist:**\n- [ ] Describe what changed and why (not just what the code does)\n- [ ] Add changelog entry to `ChangelogView.js`\n- [ ] Update README.md if architecture or routing changed\n- [ ] New components include `errorCaptured` hook + error banner (see Error Handling section)\n- [ ] Utility functions guard against invalid inputs (null, NaN, Invalid Date)\n- [ ] Screenshots or screen recording for visual changes\n- [ ] No build step required - test by serving with `python3 -m http.server 8000`\n\n### For AI Agents (Claude, etc.)\n\nWhen implementing changes in this codebase, follow these requirements before every push:\n\n1. **Update `ChangelogView.js`**: Add items under the latest `version` entry (or create a new entry). Use the correct type: `feature`, `enhancement`, or `fix`. Add a `link` to the relevant app route if applicable.\n\n2. **Update `README.md`**:\n   - If `currentRoute` values changed → update the Architecture \u003e Routing section\n   - If new components were added → update the Project Structure section\n   - If new features were added → update the Features section\n\n3. **Commit message format**: Use conventional commits (`feat:`, `fix:`, `docs:`, `refactor:`) with a short imperative summary, followed by a bulleted list of what changed and why.\n\n4. **One commit per logical task** - the user can review, roll back, or cherry-pick individual changes cleanly.\n\n5. **Do not break the no-build-step contract** - all JS must be valid in-browser ES6, loaded via `\u003cscript\u003e` tags. No imports, no bundler syntax.\n\n6. **Crash-proof all new components** - add an `errorCaptured` hook, error data property, and error banner to every component that renders dynamic data or has child components. Follow the pattern in `BrowseView.js`. Guard utility functions against null/NaN/Invalid Date inputs.\n\n### Goal\n\nThis project uses **documentation-driven development**: the README describes what FERMENT is, the changelog documents what changed and when. Contributors - human or machine - maintain this contract so the project stays readable and navigable over time. 🌟\n\n---\n\n*Made with salt, patience, and good bacteria.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flayogtima%2Fferment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flayogtima%2Fferment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flayogtima%2Fferment/lists"}