diff --git a/.agents/skills/fusion-cli/SKILL.md b/.agents/skills/fusion-cli/SKILL.md
index 7575ea9..ed40d36 100644
--- a/.agents/skills/fusion-cli/SKILL.md
+++ b/.agents/skills/fusion-cli/SKILL.md
@@ -118,6 +118,7 @@ C# (`asp-core`): `main.cs`, `*.csproj` (`net10.0`), `[Route]` / `[HttpGet]`.
### What the starter demonstrates
- `FusionBaseTemplate` at `/` (Tera templates; **not** listed in Swagger).
+- Welcome UI via built-in components: `fusion.badge`, `fusion.button`, `fusion.card`, `fusion.table` (styles: `{% include "fusion/components.css" %}`).
- `FusionBaseApi` at `api/[module]` with `version="v1"` → `/v1/api/product/…`.
- Convention verbs (`get` / `post` / …) plus one custom slot (`http_get` / `httpGet` / `[HttpGet]` with `[action]`).
- Opt-in middleware list in `main` (e.g. `request_id`, `cors`, `cache_headers`, `security_headers`, `framework_headers`). Framework does **not** auto-enable middleware; the scaffold opts in.
diff --git a/.gitignore b/.gitignore
index 1560b32..274df90 100644
--- a/.gitignore
+++ b/.gitignore
@@ -79,3 +79,8 @@ Desktop.ini
## Local secrets / overrides (keep examples/*.json samples in repo)
fusion.local.json
*.local.json
+
+
+.tmp
+.nuget
+.sentry-native
\ No newline at end of file
diff --git a/crates/fusion-core/assets/templates/fusion/base.html b/crates/fusion-core/assets/templates/fusion/base.html
index fb4c457..df4f6ab 100644
--- a/crates/fusion-core/assets/templates/fusion/base.html
+++ b/crates/fusion-core/assets/templates/fusion/base.html
@@ -5,24 +5,14 @@
{% block title %}{{ title | default(value="Fusion") }}{% endblock %}
{% block head %}{% endblock %}
diff --git a/crates/fusion-core/assets/templates/fusion/components.css b/crates/fusion-core/assets/templates/fusion/components.css
new file mode 100644
index 0000000..9c7a9c2
--- /dev/null
+++ b/crates/fusion-core/assets/templates/fusion/components.css
@@ -0,0 +1,214 @@
+/* Fusion built-in UI component styles (welcome-page look). */
+
+:root {
+ --fusion-primary: #0f172a;
+ --fusion-accent: #6366f1;
+ --fusion-muted: #64748b;
+ --fusion-border: #e2e8f0;
+ --fusion-surface: #ffffff;
+ --fusion-bg: #f8fafc;
+}
+
+.fusion-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 10px 17px;
+ border-radius: 8px;
+ border: none;
+ cursor: pointer;
+ text-decoration: none;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.2;
+ transition: 0.2s ease;
+ box-sizing: border-box;
+}
+
+.fusion-btn--primary {
+ background: var(--fusion-primary);
+ color: #fff;
+}
+
+.fusion-btn--primary:hover {
+ background: #1e293b;
+ transform: translateY(-1px);
+}
+
+.fusion-btn--secondary {
+ background: var(--fusion-surface);
+ color: #334155;
+ border: 1px solid var(--fusion-border);
+}
+
+.fusion-btn--secondary:hover {
+ background: var(--fusion-bg);
+ transform: translateY(-1px);
+}
+
+.fusion-btn--danger {
+ background: #dc2626;
+ color: #fff;
+}
+
+.fusion-btn--danger:hover {
+ background: #b91c1c;
+ transform: translateY(-1px);
+}
+
+.fusion-link {
+ color: var(--fusion-accent);
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.fusion-link:hover {
+ text-decoration: underline;
+}
+
+.fusion-card {
+ margin-top: 42px;
+ padding: 24px;
+ text-align: left;
+ background: var(--fusion-surface);
+ border: 1px solid var(--fusion-border);
+ border-radius: 14px;
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
+}
+
+.fusion-card__title {
+ font-size: 14px;
+ font-weight: 700;
+ color: #334155;
+ margin: 0 0 14px;
+}
+
+.fusion-card__body {
+ color: #334155;
+}
+
+.fusion-alert {
+ padding: 0.75rem 1rem;
+ border-radius: 0.5rem;
+ margin: 1rem 0;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.fusion-alert--info {
+ background: #dbeafe;
+ color: #1e40af;
+}
+
+.fusion-alert--success {
+ background: #dcfce7;
+ color: #166534;
+}
+
+.fusion-alert--warning {
+ background: #fef9c3;
+ color: #854d0e;
+}
+
+.fusion-alert--error {
+ background: #fee2e2;
+ color: #991b1b;
+}
+
+.fusion-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 13px;
+ border-radius: 999px;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1;
+}
+
+.fusion-badge--default {
+ background: #e2e8f0;
+ color: #334155;
+}
+
+.fusion-badge--primary {
+ background: #e0e7ff;
+ color: #3730a3;
+}
+
+.fusion-badge--success {
+ background: #ecfdf5;
+ color: #047857;
+}
+
+.fusion-badge--warning {
+ background: #fffbeb;
+ color: #b45309;
+}
+
+.fusion-badge--danger {
+ background: #fef2f2;
+ color: #b91c1c;
+}
+
+.fusion-badge__dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 50%;
+ background: currentColor;
+ flex-shrink: 0;
+}
+
+.fusion-badge--success .fusion-badge__dot {
+ background: #10b981;
+}
+
+.fusion-table-wrap {
+ width: 100%;
+ overflow-x: auto;
+ margin: 1.5rem 0;
+ border: 1px solid var(--fusion-border);
+ border-radius: 14px;
+ background: var(--fusion-surface);
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
+}
+
+.fusion-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 14px;
+ text-align: left;
+}
+
+.fusion-table caption {
+ caption-side: top;
+ padding: 14px 16px 0;
+ font-size: 14px;
+ font-weight: 700;
+ color: #334155;
+ text-align: left;
+}
+
+.fusion-table th,
+.fusion-table td {
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--fusion-border);
+ color: #334155;
+}
+
+.fusion-table th {
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--fusion-muted);
+ background: #f8fafc;
+}
+
+.fusion-table tbody tr:last-child td {
+ border-bottom: none;
+}
+
+.fusion-table tbody tr:hover td {
+ background: #f8fafc;
+}
diff --git a/crates/fusion-core/assets/templates/fusion/macros.html b/crates/fusion-core/assets/templates/fusion/macros.html
index 050caac..3ce3cb3 100644
--- a/crates/fusion-core/assets/templates/fusion/macros.html
+++ b/crates/fusion-core/assets/templates/fusion/macros.html
@@ -1,7 +1,14 @@
-{# Fusion built-in UI components (Tera 2). Use: {{}} #}
+{# Fusion built-in UI components (Tera 2).
+ Use: {{}}
+ Docs: include styles via {% include "fusion/components.css" %} or extend fusion/base.html
+#}
{% component fusion.button(label: string, href: string = "", variant: string = "primary", type: string = "button") %}
-
+{%- if href -%}
+{{ label }}
+{%- else -%}
+
+{%- endif -%}
{% endcomponent fusion.button %}
{% component fusion.link(label: string, href: string = "#", variant: string = "default") %}
@@ -10,8 +17,8 @@
{% component fusion.card(title: string = "", content: string = "") %}
- {% if title %}
{{ title }}
{% endif %}
-
{{ content | safe }}
+ {% if title %}
{{ title }}
{% endif %}
+
{% if content %}{{ content | safe }}{% else %}{{ body | safe }}{% endif %}
{% endcomponent fusion.card %}
@@ -19,6 +26,34 @@
{{ message }}
{% endcomponent fusion.alert %}
-{% component fusion.badge(label: string, variant: string = "default") %}
-{{ label }}
+{# Status/pill badge. Set dot={true} for the success-style indicator used on the welcome page. #}
+{% component fusion.badge(label: string, variant: string = "default", dot: bool = false) %}
+
+ {%- if dot -%}{%- endif -%}
+ {{ label }}
+
{% endcomponent fusion.badge %}
+
+{# Data table. Pass headers/rows as arrays, and/or nest custom markup as the body. #}
+{% component fusion.table(headers: array = [], rows: array = [], caption: string = "") %}
+
+
+ {% if caption %}{{ caption }}{% endif %}
+ {% if headers %}
+
+
+ {% for h in headers %}| {{ h }} | {% endfor %}
+
+
+ {% endif %}
+
+ {% for row in rows %}
+
+ {% for cell in row %}| {{ cell }} | {% endfor %}
+
+ {% endfor %}
+ {{ body | safe }}
+
+
+
+{% endcomponent fusion.table %}
diff --git a/crates/fusion-core/src/templates.rs b/crates/fusion-core/src/templates.rs
index 2485223..7229ded 100644
--- a/crates/fusion-core/src/templates.rs
+++ b/crates/fusion-core/src/templates.rs
@@ -9,6 +9,8 @@ use tera::{Context, Tera};
const BUILTIN_MACROS: &str = include_str!("../assets/templates/fusion/macros.html");
const BUILTIN_BASE: &str = include_str!("../assets/templates/fusion/base.html");
+const BUILTIN_COMPONENTS_CSS: &str =
+ include_str!("../assets/templates/fusion/components.css");
static ENGINE_CACHE: Mutex