diff --git a/conversation_summary.md b/conversation_summary.md new file mode 100644 index 0000000..e2df17e --- /dev/null +++ b/conversation_summary.md @@ -0,0 +1,130 @@ +# Detailed Project Architecture & Design System Reference Manual + +This document provides a comprehensive technical overview of the **Stick** template, detailing its vertical structure, core dependencies, event delegation contracts, component reuse solutions, and our current implementation plan. + +--- + +## 📁 1. Project Directory Layout & Vertical Architecture + +Unlike traditional MVC applications that split code horizontally (e.g., separating all controllers, all models, and all views), Stick is organized by **vertical features (use-cases)**. Every business module contains its own Rust handler logic, database schemas, repository queries, and custom routing. + +### Directory Mapping +```text +stick/ +├── Cargo.toml # Rust dependency management +├── package.json # Node assets configuration (optional tooling) +├── src/ +│ ├── main.rs # Core composition, shared state, and route merging +│ ├── common/ # Shared utilities & configurations +│ │ ├── config.rs # Configuration parsed from environment variables (.env) +│ │ ├── database.rs # MongoDB initialization and connections +│ │ └── errors.rs # AppError implementation wrapping custom responses +│ ├── auth/ # AUTH USE-CASE (Users, Passwords, Session cookies) +│ │ ├── extractors.rs # Native Axum extractors for authentication state +│ │ ├── handlers.rs # Login and registration endpoint handlers +│ │ ├── models.rs # User database schemas +│ │ └── repository.rs # MongoDB operations for authentication +│ ├── tasks/ # TASKS USE-CASE (CRUD tasks, dashboard view) +│ │ ├── handlers.rs +│ │ ├── models.rs +│ │ └── repository.rs +│ ├── developers/ # DEVELOPERS USE-CASE (HTMX-based assignee autocomplete query) +│ │ ├── handlers.rs +│ │ ├── models.rs +│ │ └── repository.rs +│ ├── main_view/ # STATIC VIEWS & GLOBAL ASSET ENDPOINTS +│ │ └── mod.rs # Serves index.html, static css, and static javascript files +│ ├── components/ # WIKI & INTERACTIVE DESIGN SYSTEM +│ │ ├── mod.rs # Wiki routing table +│ │ └── handlers.rs # Handlers serving reference pages +│ └── input.css # Tailwind CSS v4 custom theme mappings and custom scrollbars +├── static/ # Raw assets compiled/included in compilation +│ ├── tailwind.css # Output/input css rules +│ └── js/ +│ ├── components.js # Main component event delegation system +│ └── combobox.js # HTMX-friendly autocomplete combobox scripts +└── templates/ # Raw HTML template layout files (Askama templates) + ├── base.html # Global HTML layout wrapper (injects headers, navigation) + ├── auth/ # Login / registration markup + ├── tasks/ # Task management layouts + ├── main_view/ # Landing page layout + └── components/ # Component Wiki pages (Buttons, Modals, Comboboxes, etc.) +``` + +### Route Composition +In [src/main.rs](file:///home/enciphered/Desktop/Code/stick/src/main.rs), routers from each vertical module are instantiated and merged using Axum's `.merge()` method: +```rust +let app = Router::new() + .merge(main_view::router()) + .merge(components::router()) + .merge(auth::router()) + .merge(tasks::router()) + .merge(developers::router()) + .with_state(state); +``` + +--- + +## 🛠️ 2. Core Technical Dependencies + +* **Web Server**: `axum = "0.8.9"` — Native asynchronous routing with type-safe extractor traits. +* **Database**: `mongodb = "3.7.0"` — Offical Rust driver, using `bson = "3.1.0"` with serde serialization. +* **Template Rendering**: `askama = "0.16.0"` — Compile-time type safety; views are compiled directly into the binary. +* **Styling Engine**: Tailwind CSS (v4) — Uses native CSS variables and `@import "tailwindcss"` rather than complex configuration JSONs. +* **Security & JWT**: `jsonwebtoken = "10.4.0"` (via `rust_crypto` backend) + `bcrypt = "0.19.1"` for password hashing. JWTs are stored in secure `HttpOnly` cookie wrappers. + +--- + +## 🎨 3. JavaScript & HTML Event Delegation Contract + +Rather than attaching individual event listeners to elements upon page load (which degrades performance and fails when elements are swapped dynamically via HTMX), Stick uses **document-level event delegation** via [static/js/components.js](file:///home/enciphered/Desktop/Code/stick/static/js/components.js) and [static/js/combobox.js](file:///home/enciphered/Desktop/Code/stick/static/js/combobox.js). + +### Active Binding Attributes & Hooks +To make components function, developers must preserve specific HTML attributes and class structures that JavaScript expects: + +| Component Type | JavaScript Trigger / Hook | Expected Target / Functional Structure | +| :--- | :--- | :--- | +| **Modal / Dialog** | `[data-modal-target="id"]` | `.modal-dialog` (wrapper), `.modal-backdrop`, `.modal-close` | +| **Sheets / Drawers** | `[data-sheet-target="id"]` | `.sheet-dialog` (wrapper), `.sheet-backdrop`, `.sheet-close` | +| **Custom Dropdowns** | `.dropdown-trigger` | `.dropdown-menu` (container), `.dropdown-content` | +| **Interactive Tabs**| `[data-tab-target="pane-id"]` | `[data-tab-group="group-name"]` on buttons & content wrappers | +| **Accordions** | `.accordion-trigger` | `.accordion-item` (container), `.accordion-content`, `.accordion-chevron` | +| **Custom Select** | `.select-trigger` | `.custom-select` (container), `.select-popover`, `.select-item`, `.select-value` | +| **Autocomplete Combobox**| `.combobox-input` | `.autocomplete-combobox` (container), `.combobox-results`, `.combobox-value` | + +*Any customized styling classes (colors, borders, rounded corners) can be freely added or changed. However, the core class naming hierarchy listed above must remain intact to preserve interaction animations and keyboard controls.* + +--- + +## 🧩 4. Proposed Scalability & Code Reuse Solutions + +### Solution 1: Askama Macros (Selected Demo) +Consolidate UI components into a global macros registry template. +* *Pros*: Safe compilation (missing variables or typos are checked at build time), zero performance cost. +* *Cons*: Complex child structures (e.g. slots or dynamic SVG parameters) require passing strings/blocks, which can look verbose. + +### Solution 2: CSS Component Classes +Define custom components in [src/input.css](file:///home/enciphered/Desktop/Code/stick/src/input.css) using Tailwind CSS v4's components layer (e.g. `@apply btn btn-primary`). +* *Pros*: Clear, descriptive markup syntax; easily consumable by pure front-end web developers. +* *Cons*: Partially moves styling definitions out of HTML files and back into stylesheet files. + +### Solution 3: Rust Struct Components +Represent reusable buttons, grids, or select widgets as Rust structs that implement Askama's `Template` trait. +* *Pros*: Advanced data-driven structures and type constraints managed entirely in Rust code. +* *Cons*: Visual updates require recompiling Rust binaries, which limits swift CSS experimentation. + +--- + +## 🚀 5. Action Plan: Askama Macros Implementation +We are implementing a demo showcasing Askama Macros in a new git branch: + +1. **Branch Setup**: Create `demo/askama-macros`. +2. **Define Macros**: Create `templates/components/macros.html` to define reusable snippets for: + * **Button**: Primary, secondary, outline, destructive, status indicators. + * **Modal**: Accessibility markup, background overlays, closing controls. + * **Autocomplete Combobox**: Dynamic HTMX data queries and search hooks. +3. **Refactor Views**: Replace manual HTML blocks in [dashboard.html](file:///home/enciphered/Desktop/Code/stick/templates/tasks/dashboard.html) with: + ```html + {% import "components/macros.html" as ui %} + {{ ui::button("Submit", "primary") }} + ``` diff --git a/static/js/combobox.js b/static/js/combobox.js index bc0ca92..5f7b202 100644 --- a/static/js/combobox.js +++ b/static/js/combobox.js @@ -185,7 +185,6 @@ document.addEventListener('DOMContentLoaded', () => { if (input) { input.value = name; input.focus(); - input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); } diff --git a/templates/auth/login.html b/templates/auth/login.html index b57a634..ae32342 100644 --- a/templates/auth/login.html +++ b/templates/auth/login.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% import "components/macros.html" as ui %} {% block title %}Sign In - Stick{% endblock %} @@ -22,20 +23,12 @@ {% endif %}
diff --git a/templates/auth/register.html b/templates/auth/register.html index 8c8082e..f2519fc 100644 --- a/templates/auth/register.html +++ b/templates/auth/register.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% import "components/macros.html" as ui %} {% block title %}Sign Up - Stick{% endblock %} @@ -31,20 +32,12 @@ {% endif %} diff --git a/templates/components/buttons.html b/templates/components/buttons.html index 6dc76a8..92e964d 100644 --- a/templates/components/buttons.html +++ b/templates/components/buttons.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% import "components/macros.html" as ui %} {% block title %}Buttons - Design System Wiki{% endblock %} @@ -33,82 +34,44 @@<!-- Server-Side Combobox Container (Asynchronous Query via HTMX) -->
-<div class="autocomplete-combobox relative">
- <!-- Holds the final value submitted to forms -->
- <input type="hidden" name="assignee_id" class="combobox-value">
-
- <!-- Input box triggers search query. Submits with parameter 'q' -->
- <input type="text" name="q" placeholder="Search developers..." autocomplete="off"
- class="combobox-input block w-full px-4 py-2 bg-background border border-border rounded-xl text-sm focus:ring-2 focus:ring-sky-500"
- hx-get="/developers/search" hx-trigger="input changed delay:250ms" hx-target="next .combobox-results">
-
- <!-- Dropdown container receives swapped HTML markup from server -->
- <div class="combobox-results absolute z-10 w-full mt-2 bg-popover border border-border rounded-xl p-1 shadow-xl hidden"></div>
-</div>
+ {{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
+
+<!-- Server-Side Combobox using macro (Asynchronous Query via HTMX) -->
+{{ "{{" }} ui::search_combobox(name="assignee_id", label="Live MongoDB Query", placeholder="Query developers database...", search_url="/developers/search", input_id="wiki-db-search", value_id="wiki-db-value") {{ "}}" }}
<!-- Styled Text Input -->
-<div class="space-y-2">
- <label class="block text-xs font-semibold text-muted-foreground">Username Input</label>
- <input type="text" placeholder="e.g. dev_alice"
- class="block h-10 w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200">
-</div>
+ {{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
+
+<!-- Styled Text Input -->
+{{ "{{" }} ui::text_input(id="demo-username", name="username", label="Username Input", type="text", placeholder="e.g. dev_alice") {{ "}}" }}
+
+<!-- Styled Password Input -->
+{{ "{{" }} ui::text_input(id="demo-password", name="password", label="Security Password", type="password", placeholder="••••••••") {{ "}}" }}
<!-- Styled Textarea -->
-<div class="space-y-2">
- <label class="block text-xs font-semibold text-muted-foreground">Task Details</label>
- <textarea rows="3" placeholder="Provide a detailed description..."
- class="block w-full rounded-xl border border-border bg-background px-4 py-2 text-sm text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500/50 transition duration-200 resize-none"></textarea>
-</div>
+{{ "{{" }} ui::textarea(id="demo-details", name="details", label="Task Details", placeholder="Provide a detailed description...") {{ "}}" }}
-<!-- Custom Styled Select Dropdown (Chevron and Popover managed globally in components.js) -->
-<div class="space-y-2">
- <label class="block text-xs font-semibold text-muted-foreground">Developer Specialization</label>
- <div class="custom-select relative inline-block w-full">
- <!-- Hidden input holds the actual value for form submissions -->
- <input type="hidden" name="specialization" class="select-value" value="Senior Rust Engineer">
-
- <!-- Toggle trigger button -->
- <button type="button" class="select-trigger flex h-10 w-full items-center justify-between rounded-xl border border-border bg-background px-4 py-2 text-sm text-slate-200">
- <span class="select-text">Senior Rust Engineer</span>
- <svg class="h-4 w-4 text-slate-500 transition-transform duration-200 select-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
- <polyline points="6 9 12 15 18 9"/>
- </svg>
- </button>
-
- <!-- Options Popover -->
- <div class="select-popover absolute left-0 z-20 mt-2 w-full p-1 rounded-2xl border border-border bg-popover shadow-2xl hidden">
- <div class="max-h-60 overflow-y-auto p-0.5 space-y-0.5">
- <button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs bg-accent text-accent-foreground font-semibold text-slate-200 text-left" data-value="Senior Rust Engineer">Senior Rust Engineer</button>
- <button type="button" class="select-item flex items-center w-full h-9 px-2.5 rounded-lg text-xs hover:bg-accent hover:text-accent-foreground text-slate-200 text-left" data-value="Frontend Architect">Frontend Architect</button>
- </div>
- </div>
- </div>
-</div>
+<!-- Custom Styled Select Dropdown -->
+{{ "{{" }} ui::select_open(name="specialization", label="Developer Specialization", current_value="Senior Rust Engineer", current_text="Senior Rust Engineer") {{ "}}" }}
+ {{ "{{" }} ui::select_item(value="Senior Rust Engineer", label="Senior Rust Engineer", is_selected=true) {{ "}}" }}
+ {{ "{{" }} ui::select_item(value="Frontend Architect", label="Frontend Architect") {{ "}}" }}
+ {{ "{{" }} ui::select_item(value="DevOps Lead", label="DevOps Lead") {{ "}}" }}
+{{ "{{" }} ui::select_close() {{ "}}" }}
diff --git a/templates/components/macros.html b/templates/components/macros.html
new file mode 100644
index 0000000..5990608
--- /dev/null
+++ b/templates/components/macros.html
@@ -0,0 +1,383 @@
+{% macro button(label, variant="primary", type="button", extra_class="", disabled=false) %}
+
+{% endmacro %}
+
+{% macro modal_trigger(target_id, label, variant="primary", extra_class="") %}
+
+{% endmacro %}
+
+{% macro sheet_trigger(target_id, label, variant="primary", extra_class="") %}
+
+{% endmacro %}
+
+{% macro modal(id, title, content, close_label="Close") %}
+
+{% endmacro %}
+
+{% macro modal_open(id, title) %}
+
+{% endmacro %}
+
+{% macro sheet_open(id, title, max_width_class="max-w-sm") %}
+
+{% endmacro %}
+
+{% macro search_combobox(name, label, placeholder="Search...", search_url="/developers/search", input_id="combobox-search", value_id="combobox-value") %}
+<!-- Trigger Button (points to modal element ID) -->
-<button data-modal-target="my-modal-id" class="px-4 py-2 bg-indigo-600 text-white text-xs font-bold rounded-xl">
- Open Modal
-</button>
+ {{ "{%" }} import "components/macros.html" as ui {{ "%}" }}
-<!-- Modal Overlay Element -->
-<div id="my-modal-id" class="modal-dialog fixed inset-0 z-50 flex items-center justify-center hidden" role="dialog" aria-modal="true">
- <!-- Backdrop shadow with blur -->
- <div class="modal-backdrop fixed inset-0 bg-[#07090e]/80 backdrop-blur-sm transition-opacity duration-300"></div>
-
- <!-- Modal Panel with scale / opacity transition -->
- <div class="modal-content relative z-10 w-full max-w-sm scale-95 opacity-0 transition-all duration-300 border border-border bg-popover/95 backdrop-blur-xl p-6 shadow-2xl rounded-3xl">
- <h3 class="text-sm font-bold text-slate-100">Confirmation Title</h3>
- <p class="text-xs text-muted-foreground mt-2 leading-relaxed">Are you sure you want to proceed?</p>
-
- <!-- Close triggers require class 'modal-close' -->
- <button class="modal-close mt-4 w-full py-2 rounded-xl bg-secondary border border-border text-slate-200 text-xs font-semibold">
- Cancel
- </button>
- </div>
-</div>
+<!-- Trigger Button (points to modal element ID) -->
+{{ "{{" }} ui::modal_trigger(target_id="my-modal-id", label="Open Modal Dialog", variant="indigo") {{ "}}" }}
+
+<!-- Option A: Simple Modal (Self-contained with text body) -->
+{{ "{{" }} ui::modal(id="my-modal-id", title="Confirmation Title", content="Are you sure you want to proceed?", close_label="Cancel") {{ "}}" }}
+
+<!-- Option B: Paired Macros (For custom markup, inputs, or headers) -->
+{{ "{{" }} ui::modal_open(id="my-modal-id", title="Confirmation Title") {{ "}}" }}
+ <p class="text-xs text-muted-foreground mt-2 leading-relaxed">Are you sure you want to proceed?</p>
+{{ "{{" }} ui::modal_close(close_label="Cancel") {{ "}}" }}
@@ -136,20 +126,10 @@
-
-
+
+{{ ui::modal_open(id="wiki-demo-modal", title="Wiki Sandbox Modal") }}
+ This modal is animated and handled globally by components.js. Pressing escape or clicking outside closes it instantly.