feat: first cut on M0 - base structure of project
This commit is contained in:
107
docs/LIQUID_FEATURE_INVENTORY.md
Normal file
107
docs/LIQUID_FEATURE_INVENTORY.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# bDS Liquid Feature Inventory
|
||||
|
||||
Inventoried from the 12 default templates in the TypeScript app (`src/main/engine/templates/`).
|
||||
|
||||
## Template Files Analyzed
|
||||
|
||||
1. `single-post.liquid` (main)
|
||||
2. `post-list.liquid` (main)
|
||||
3. `not-found.liquid` (main)
|
||||
4. `macros/gallery.liquid`
|
||||
5. `macros/youtube.liquid`
|
||||
6. `macros/vimeo.liquid`
|
||||
7. `macros/photo-archive.liquid`
|
||||
8. `macros/tag-cloud.liquid`
|
||||
9. `partials/head.liquid`
|
||||
10. `partials/menu.liquid`
|
||||
11. `partials/menu-items.liquid`
|
||||
12. `partials/language-switcher.liquid`
|
||||
|
||||
## Tags Used
|
||||
|
||||
| Tag | Used In |
|
||||
|---|---|
|
||||
| `if` / `endif` | 9 templates |
|
||||
| `elsif` | post-list |
|
||||
| `else` | 7 templates |
|
||||
| `for` / `endfor` | 7 templates |
|
||||
| `assign` | 4 templates (not-found, post-list, head, single-post) |
|
||||
| `render` | 5 templates (menu, menu-items, not-found, post-list, single-post) |
|
||||
|
||||
### NOT Used
|
||||
|
||||
`unless`, `case/when`, `capture`, `layout`, `include`, `comment`, `raw`, `increment`, `decrement`, `tablerow`, `cycle`
|
||||
|
||||
## Filters Used
|
||||
|
||||
| Filter | Type | Signature |
|
||||
|---|---|---|
|
||||
| `escape` | Built-in | `\| escape` |
|
||||
| `default` | Built-in | `\| default: value` |
|
||||
| `append` | Built-in | `\| append: string` |
|
||||
| `url_encode` | Built-in | `\| url_encode` |
|
||||
| `i18n` | **Custom** | `\| i18n: language` — translation lookup by key and language |
|
||||
| `markdown` | **Custom** | `\| markdown: post.id, post_data_json_by_id, canonical_post_path_by_slug, canonical_media_path_by_source_path, language, language_prefix` — markdown-to-HTML with macro expansion and link resolution (6 args) |
|
||||
|
||||
### NOT Used
|
||||
|
||||
`date`, `truncate`, `split`, `join`, `where`, `group_by`, `map`, `sort`, `reverse`, `size` (as pipe filter), `strip`, `strip_html`, `downcase`, `upcase`, `replace`, `remove`, `first`, `last`, `abs`, `ceil`, `floor`, `round`, `plus`, `minus`, `times`, `divided_by`, `modulo`
|
||||
|
||||
## Operators
|
||||
|
||||
| Operator | Example |
|
||||
|---|---|
|
||||
| `==` | `item.href == '#'`, `archive_context.kind == 'tag'` |
|
||||
| `>` | `menu_items.size > 0`, `blog_languages.size > 1` |
|
||||
| `and` | `menu_items and menu_items.size > 0` |
|
||||
| `or` | `archive_context.kind == 'tag' or archive_context.kind == 'category'` |
|
||||
| `blank` | `canonical_post_href == blank` (nil/empty check) |
|
||||
| bare truthiness | `{% if html_theme_attribute %}`, `{% if caption %}` |
|
||||
|
||||
### NOT Used
|
||||
|
||||
`!=`, `<`, `<=`, `>=`, `contains`
|
||||
|
||||
## Property Access Patterns
|
||||
|
||||
| Pattern | Examples |
|
||||
|---|---|
|
||||
| Dot notation | `archive_context.kind`, `post.title`, `item.media_path`, `lang.is_current` |
|
||||
| `.size` property | `menu_items.size`, `items.size`, `post_categories.size` |
|
||||
| Bracket notation (hash/map lookup) | `canonical_post_path_by_slug[post.slug]`, `tag_color_by_name[tag]` |
|
||||
|
||||
## Whitespace Stripping
|
||||
|
||||
`{%- -%}` used in 3 macro templates only: `photo-archive`, `gallery`, `tag-cloud`.
|
||||
|
||||
## For-Loop Features
|
||||
|
||||
| Feature | Used? |
|
||||
|---|---|
|
||||
| Basic `for x in collection` | YES |
|
||||
| Nested `for` loops | YES (photo-archive, post-list) |
|
||||
| Recursive `render` in loop | YES (menu-items renders itself for children) |
|
||||
| `forloop.first` / `forloop.last` | NO |
|
||||
| `limit` / `offset` | NO |
|
||||
| `reversed` | NO |
|
||||
|
||||
## Render Tag Usage
|
||||
|
||||
- Uses named parameter passing: `{% render 'partials/menu-items', items: menu_items, include_calendar: true, language: language %}`
|
||||
- Recursive self-render: menu-items calls `render 'partials/menu-items'` for nested children
|
||||
- Partial paths use forward-slash notation: `'partials/head'`, `'partials/menu'`
|
||||
|
||||
## Two-Step Dynamic i18n Keys
|
||||
|
||||
`{% assign month_key = 'render.month.' | append: archive_context.month %}` then `{{ month_key | i18n: language }}`
|
||||
|
||||
## Scope Summary
|
||||
|
||||
The Rust Liquid implementation needs approximately **35% of the full specification**:
|
||||
|
||||
- **Tags**: `if`/`elsif`/`else`, `for`, `assign`, `render` (not `include`)
|
||||
- **Built-in filters**: `escape`, `default`, `append`, `url_encode`
|
||||
- **Custom filters**: `i18n` (key + language), `markdown` (content + 6 args)
|
||||
- **Operators**: `==`, `>`, `and`, `or`, truthiness, `blank`
|
||||
- **Access**: dot notation, `.size` property, bracket notation for hash/map lookups
|
||||
- **Whitespace stripping**: `{%- -%}` support required
|
||||
114
docs/RESEARCH_FINDINGS.md
Normal file
114
docs/RESEARCH_FINDINGS.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# bDS Rust Rewrite — Research Findings
|
||||
|
||||
Research completed during M0 from the TypeScript codebase at `../bDS/`.
|
||||
|
||||
## Database Schema (16 migrations, 0000–0015)
|
||||
|
||||
### Active Tables
|
||||
|
||||
| Table | Primary Key | Key Columns |
|
||||
|---|---|---|
|
||||
| `projects` | `id TEXT` | name, slug, description, data_path, is_active, created_at, updated_at |
|
||||
| `settings` | `key TEXT` | value, updated_at |
|
||||
| `posts` | `id TEXT` | project_id, title, slug, excerpt, content, status, author, language, do_not_translate, template_slug, file_path, checksum, tags, categories, published_title/content/tags/categories/excerpt, created_at, updated_at, published_at |
|
||||
| `post_translations` | `id TEXT` | project_id, translation_for, language, title, excerpt, content, status, file_path, checksum, created_at, updated_at, published_at |
|
||||
| `post_links` | `id TEXT` | source_post_id, target_post_id, link_text, created_at |
|
||||
| `post_media` | `id TEXT` | project_id, post_id, media_id, sort_order, created_at |
|
||||
| `media` | `id TEXT` | project_id, filename, original_name, mime_type, size, width, height, title, alt, caption, author, language, file_path, sidecar_path, checksum, tags, created_at, updated_at |
|
||||
| `media_translations` | `id TEXT` | project_id, translation_for, language, title, alt, caption, created_at, updated_at |
|
||||
| `tags` | `id TEXT` | project_id, name, color, post_template_slug, created_at, updated_at |
|
||||
| `templates` | `id TEXT` | project_id, slug, title, kind, enabled, version, file_path, status, content, created_at, updated_at |
|
||||
| `scripts` | `id TEXT` | project_id, slug, title, kind, entrypoint, enabled, version, file_path, status, content, created_at, updated_at |
|
||||
| `generated_file_hashes` | (project_id, relative_path) UNIQUE | content_hash, updated_at |
|
||||
| `db_notifications` | `id INTEGER AUTOINCREMENT` | entity, entity_id, action, from_cli, seen_at, created_at |
|
||||
| `chat_conversations` | `id TEXT` | title, model, copilot_session_id, created_at, updated_at |
|
||||
| `chat_messages` | `id INTEGER AUTOINCREMENT` | conversation_id, role, content, tool_call_id, tool_calls, created_at |
|
||||
| `import_definitions` | `id TEXT` | project_id, name, wxr_file_path, uploads_folder_path, last_analysis_result, created_at, updated_at |
|
||||
| `ai_models` | (provider, model_id) | name, family, many feature flags, pricing, context_window, etc. |
|
||||
| `ai_catalog_meta` | `key TEXT` | value |
|
||||
| `ai_model_modalities` | (provider, model_id, direction, modality) | — |
|
||||
| `ai_providers` | `id TEXT` | name, env, npm, api, doc, updated_at |
|
||||
| `dismissed_duplicate_pairs` | `id TEXT` | project_id, post_id_a, post_id_b, dismissed_at |
|
||||
| `embedding_keys` | `label INTEGER` | post_id, project_id, content_hash, vector (blob) |
|
||||
|
||||
### Dropped Tables
|
||||
- `sync_log` (dropped in 0001)
|
||||
- `model_catalog`, `model_catalog_meta` (dropped in 0009, replaced by ai_models etc.)
|
||||
|
||||
### Timestamps
|
||||
All timestamps are **Unix integers** (seconds since epoch), NOT ISO strings.
|
||||
|
||||
### FTS5
|
||||
**No FTS5 virtual tables in migrations.** FTS is created at runtime.
|
||||
|
||||
### Unique Indexes
|
||||
- `posts_project_slug_idx` (project_id, slug)
|
||||
- `post_translations_translation_language_idx` (translation_for, language)
|
||||
- `media_translations_translation_language_idx` (translation_for, language)
|
||||
- `tags_project_name_idx` (project_id, name)
|
||||
- `templates_project_slug_idx` (project_id, slug)
|
||||
- `scripts_project_slug_idx` (project_id, slug)
|
||||
- `post_media_post_media_idx` (post_id, media_id)
|
||||
- `generated_file_hashes_project_path_idx` (project_id, relative_path)
|
||||
- `dismissed_pairs_idx` (project_id, post_id_a, post_id_b)
|
||||
|
||||
## File Format Details
|
||||
|
||||
### Post Files
|
||||
- Path: `posts/YYYY/MM/{slug}.md`
|
||||
- Frontmatter (YAML via gray-matter): id, title, slug, status, createdAt, updatedAt, tags, categories
|
||||
- Conditional: excerpt, author, language, doNotTranslate, templateSlug, publishedAt
|
||||
- Body: markdown after frontmatter
|
||||
|
||||
### Translation Files
|
||||
- Path: `posts/YYYY/MM/{slug}.{lang}.md` (same dir as source post)
|
||||
- Frontmatter: translationFor (UUID), language, title, excerpt (optional)
|
||||
- Body: translated content
|
||||
|
||||
### Media Sidecar Files
|
||||
- Path: `{media-file}.meta` (canonical), `{media-file}.{lang}.meta` (translation)
|
||||
- Format: hand-built YAML-like (NOT gray-matter), delimited by `---`
|
||||
- Canonical fields: id, originalName, mimeType, size, createdAt, updatedAt, tags, width?, height?, title?, alt?, caption?, author?, language?, linkedPostIds?
|
||||
- Translation fields: translationFor, language, title?, alt?, caption?
|
||||
|
||||
### Menu Document
|
||||
- Path: `meta/menu.opml` (OPML 2.0)
|
||||
- Library: fast-xml-parser (XMLParser/XMLBuilder)
|
||||
- Item kinds: page, submenu, category-archive, home
|
||||
- Home entry always enforced at position [0]
|
||||
|
||||
### Metadata JSON Files
|
||||
| File | Managed By |
|
||||
|---|---|
|
||||
| `meta/project.json` | MetaEngine |
|
||||
| `meta/categories.json` | MetaEngine |
|
||||
| `meta/category-meta.json` | MetaEngine |
|
||||
| `meta/publishing.json` | MetaEngine |
|
||||
| `meta/tags.json` | TagEngine |
|
||||
| `meta/menu.opml` | MenuEngine |
|
||||
|
||||
## Pagefind Integration
|
||||
- npm dependency: `pagefind@^1.4.0`
|
||||
- Invoked as CLI binary (`pagefind_extended`) via child process
|
||||
- Generates per-language indexes: `{html}/pagefind/`, `{html}/{lang}/pagefind/`
|
||||
- Loaded from templates via `<link>` and `<script>` tags in `partials/head.liquid`
|
||||
- Post body marked with `data-pagefind-body` attribute
|
||||
- **Rust plan:** Use `pagefind` crate library API instead of CLI
|
||||
|
||||
## Slug Generation
|
||||
- TypeScript: `transliterate(input).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')`
|
||||
- Rust: `deunicode(input)` + same pipeline
|
||||
- Need corpus comparison test to verify edge cases
|
||||
|
||||
## Publishing
|
||||
- `meta/publishing.json`: sshHost, sshUser, sshRemotePath, sshMode (scp|rsync)
|
||||
- SSH agent auth only (SSH_AUTH_SOCK), no passwords
|
||||
- Three parallel upload targets: html/, thumbnails/, media/ (excluding .meta)
|
||||
|
||||
## MetadataDiff Compared Fields
|
||||
- Posts: tags, categories, title, excerpt, author, language, translationFor, doNotTranslate, status, templateSlug, createdAt, updatedAt, publishedAt
|
||||
- Translations: translationFor, language, title, excerpt
|
||||
- Media: title, alt, caption, author, tags, language
|
||||
- Scripts: title, kind, entrypoint, enabled, version
|
||||
- Templates: title, kind, enabled, version
|
||||
- Supports bidirectional sync (DB→File, File→DB) + orphan detection
|
||||
Reference in New Issue
Block a user