@t8n/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,276 @@
1
+ ````md
2
+ # @titanpl/ui
3
+
4
+ A **minimal, explicit UI extension for TitanPL** that lets you render HTML and inject CSS **in a single render call** — with **no static routes**, **no magic**, and **full control**.
5
+
6
+ This extension is designed for **action-based routing** and **runtime-first** TitanPL apps.
7
+
8
+ ---
9
+
10
+ ## ✨ Features
11
+
12
+ - `t.ui.render()` → render HTML + CSS in one call
13
+ - `t.ui.load()` → preload templates for reuse
14
+ - `t.ui.css()` → load CSS manually if needed
15
+ - Simple templating: `tpl{{ var }}`
16
+ - Explicit CSS injection (no hidden parsing)
17
+ - Memory + persistent (`ls`) caching
18
+ - Zero static file server
19
+ - Zero extra routes
20
+ - One extension, one mental model
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ ```bash
27
+ npm install @titanpl/ui
28
+ ````
29
+
30
+ TitanPL automatically loads the extension at runtime.
31
+
32
+ ---
33
+
34
+ ## 📁 Project Structure
35
+
36
+ ```
37
+ app/
38
+ ├─ actions/
39
+ │ └─ hello.js
40
+ ├─ static/
41
+ │ ├─ app.html
42
+ │ └─ styles.css
43
+ ```
44
+
45
+ All paths are **relative to `app/`**.
46
+
47
+ ---
48
+
49
+ ## 🧠 Core Idea
50
+
51
+ TitanPL does **nothing implicitly**.
52
+
53
+ * HTML is rendered only when you call `t.ui.render()` or `t.ui.load()`
54
+ * CSS is injected **only if you explicitly request it**
55
+ * You decide where CSS goes in HTML
56
+
57
+ ---
58
+
59
+ ## 🧩 Template Syntax
60
+
61
+ ### Variables
62
+
63
+ ```html
64
+ tpl{{ name }}
65
+ ```
66
+
67
+ ### CSS injection point
68
+
69
+ ```html
70
+ tpl{{ css }}
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 🚀 Usage
76
+
77
+ ### 1️⃣ HTML Template
78
+
79
+ `app/static/app.html`
80
+
81
+ ```html
82
+ <!DOCTYPE html>
83
+ <html>
84
+ <head>
85
+ tpl{{ css }}
86
+ </head>
87
+ <body>
88
+ <h1>tpl{{ name }}</h1>
89
+ </body>
90
+ </html>
91
+ ```
92
+
93
+ ---
94
+
95
+ ### 2️⃣ CSS File
96
+
97
+ `app/static/styles.css`
98
+
99
+ ```css
100
+ body {
101
+ background-color: black;
102
+ height: 100vh;
103
+ }
104
+
105
+ h1 {
106
+ color: rgb(32, 215, 215);
107
+ }
108
+ ```
109
+
110
+ ---
111
+
112
+ ### 3️⃣ One-Shot Render (Recommended)
113
+
114
+ ```js
115
+ export const hello = () => {
116
+ return t.ui.render(
117
+ "static/app.html",
118
+ { name: "Titan" },
119
+ { css: "static/styles.css" }
120
+ );
121
+ };
122
+ ```
123
+
124
+ ✔ HTML rendered
125
+ ✔ CSS injected
126
+ ✔ Single response
127
+ ✔ No static routes
128
+
129
+ ---
130
+
131
+ ### 4️⃣ Multiple CSS Files
132
+
133
+ ```js
134
+ return t.ui.render(
135
+ "static/app.html",
136
+ { name: "Titan" },
137
+ { css: ["static/base.css", "static/theme.css"] }
138
+ );
139
+ ```
140
+
141
+ CSS files are injected **in order**.
142
+
143
+ ---
144
+
145
+ ## 🔁 Preloading Templates with `t.ui.load()`
146
+
147
+ Use this for **high-traffic routes**.
148
+
149
+ ```js
150
+ const page = t.ui.load("static/app.html");
151
+
152
+ export const hello = () => {
153
+ return page(
154
+ { name: "Titan" },
155
+ { css: "static/styles.css" }
156
+ );
157
+ };
158
+ ```
159
+
160
+ ### Why use `load()`?
161
+
162
+ * Template read once
163
+ * Faster per request
164
+ * Cleaner route code
165
+
166
+ ---
167
+
168
+ ## 🎯 Manual CSS Loading (Optional)
169
+
170
+ If you want full control:
171
+
172
+ ```js
173
+ export const hello = () => {
174
+ return t.ui.render("static/app.html", {
175
+ name: "Titan",
176
+ css: t.ui.css("static/styles.css")
177
+ });
178
+ };
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 🧠 API Reference
184
+
185
+ ### `t.ui.render(htmlPath, data?, options?)`
186
+
187
+ Render HTML once with optional CSS.
188
+
189
+ ```js
190
+ t.ui.render(
191
+ "static/app.html",
192
+ { name: "Titan" },
193
+ { css: "static/styles.css" }
194
+ );
195
+ ```
196
+
197
+ ---
198
+
199
+ ### `t.ui.load(htmlPath)`
200
+
201
+ Preload template and return a reusable renderer.
202
+
203
+ ```js
204
+ const page = t.ui.load("static/app.html");
205
+ page(data, options);
206
+ ```
207
+
208
+ ---
209
+
210
+ ### `t.ui.css(cssPath)`
211
+
212
+ Load CSS and return a `<style>` block.
213
+
214
+ ```js
215
+ t.ui.css("static/styles.css");
216
+ ```
217
+
218
+ ---
219
+
220
+ ### `t.ui.clearCache()`
221
+
222
+ Clear all cached HTML and CSS files.
223
+
224
+ ```js
225
+ t.ui.clearCache();
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 🧠 Mental Model (Important)
231
+
232
+ ```text
233
+ t.ui.render() → returns an HTML response
234
+ tpl{{ css }} → where styles are injected
235
+ tpl{{ var }} → template variables
236
+ YOU → control composition
237
+ ```
238
+
239
+ No implicit behavior.
240
+ No auto static serving.
241
+ No hidden parsing.
242
+
243
+ ---
244
+
245
+ ## ❌ What This Extension Does NOT Do
246
+
247
+ * ❌ No static file server
248
+ * ❌ No `<link>` interception
249
+ * ❌ No auto CSS loading
250
+ * ❌ No HTML AST parsing
251
+ * ❌ No framework magic
252
+
253
+ This is **intentional**.
254
+
255
+ ---
256
+
257
+ ## ✅ Why This Fits TitanPL
258
+
259
+ * Runtime-first
260
+ * Predictable
261
+ * Explicit IO
262
+ * Action-based routing friendly
263
+ * Production-safe
264
+ * Easy to debug
265
+
266
+ ---
267
+
268
+ ## 🛣️ Possible Extensions (Future)
269
+
270
+ * `t.ui.js()` for inline JS
271
+ * Layouts / partials
272
+ * Scoped CSS
273
+ * Dot-path vars (`tpl{{ user.name }}`)
274
+ * HTML escaping / raw blocks
275
+
276
+
package/index.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ // Type definitions for @titanpl/ui
2
+ // This file enables type inference when the UI extension
3
+ // is installed in a TitanPL project.
4
+
5
+ declare global {
6
+ namespace Titan {
7
+ interface Runtime {
8
+ /**
9
+ * @titanpl/ui Extension
10
+ *
11
+ * Unified UI rendering utilities for TitanPL
12
+ * (HTML + CSS, runtime-first, action-safe)
13
+ */
14
+ ui: {
15
+ /**
16
+ * Render an HTML template in one call.
17
+ *
18
+ * @param htmlPath Path relative to `app/`
19
+ * @param data Template variables (tpl{{ var }})
20
+ * @param options Optional render options
21
+ */
22
+ render(
23
+ htmlPath: string,
24
+ data?: Record<string, any>,
25
+ options?: {
26
+ /**
27
+ * CSS file or files to inline
28
+ * Paths are relative to `app/`
29
+ */
30
+ css?: string | string[];
31
+ }
32
+ ): unknown; // Titan Response
33
+
34
+ /**
35
+ * Preload an HTML template and return a reusable renderer.
36
+ *
37
+ * @param htmlPath Path relative to `app/`
38
+ */
39
+ load(
40
+ htmlPath: string
41
+ ): (
42
+ data?: Record<string, any>,
43
+ options?: {
44
+ css?: string | string[];
45
+ }
46
+ ) => unknown; // Titan Response
47
+
48
+ /**
49
+ * Load a CSS file and return a <style> string.
50
+ *
51
+ * @param cssPath Path relative to `app/`
52
+ */
53
+ css(cssPath: string): string;
54
+
55
+ /**
56
+ * Clear all cached UI templates and styles.
57
+ */
58
+ clearCache(): boolean;
59
+ };
60
+ }
61
+ }
62
+ }
63
+
64
+ export {};
65
+
package/index.js ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * TitanPL UI Extension (HTML + CSS)
3
+ * Provides:
4
+ * t.ui.load()
5
+ * t.ui.render()
6
+ * t.ui.css()
7
+ * t.ui.clearCache()
8
+ */
9
+
10
+ if (typeof Titan === "undefined") globalThis.Titan = t;
11
+
12
+ const EXT_KEY = "ui";
13
+ const { fs, ls } = t.core;
14
+
15
+ // --------------------------------------------------
16
+ // Internal cache (per isolate)
17
+ // --------------------------------------------------
18
+ const memoryCache = Object.create(null);
19
+
20
+ // --------------------------------------------------
21
+ // Resolve paths relative to app/
22
+ // --------------------------------------------------
23
+ function resolvePath(path) {
24
+ const root = "../app" || "."
25
+ return root + `/${path.replace(/^\/+/, "")}`;
26
+ }
27
+
28
+ // --------------------------------------------------
29
+ // Load file with memory + persistent cache
30
+ // --------------------------------------------------
31
+ function loadFile(path, prefix) {
32
+ if (memoryCache[path]) return memoryCache[path];
33
+
34
+ const cached = ls.get(`${prefix}:${path}`);
35
+ if (cached) {
36
+ memoryCache[path] = cached;
37
+ return cached;
38
+ }
39
+
40
+ const content = fs.readFile(path);
41
+ memoryCache[path] = content;
42
+ ls.set(`${prefix}:${path}`, content);
43
+
44
+ return content;
45
+ }
46
+
47
+ // --------------------------------------------------
48
+ // Render tpl{{ var }}
49
+ // --------------------------------------------------
50
+ function renderTemplate(template, data = {}) {
51
+ return template.replace(
52
+ /tpl\{\{\s*(\w+)\s*\}\}/g,
53
+ (m, k) => (k in data ? String(data[k]) : m)
54
+ );
55
+ }
56
+
57
+ // --------------------------------------------------
58
+ // Inject CSS into render data
59
+ // --------------------------------------------------
60
+ function injectCSS(data, opts = {}) {
61
+ if (!opts.css) return data;
62
+
63
+ const cssFiles = Array.isArray(opts.css) ? opts.css : [opts.css];
64
+
65
+ const styles = cssFiles
66
+ .map(p => {
67
+ const css = loadFile(resolvePath(p), "css");
68
+ return `<style>\n${css}\n</style>`;
69
+ })
70
+ .join("\n");
71
+
72
+ return {
73
+ ...data,
74
+ css: styles
75
+ };
76
+ }
77
+
78
+ // --------------------------------------------------
79
+ // EXTENSION CONTRACT (MANDATORY FOR TITANPL)
80
+ // --------------------------------------------------
81
+ t[EXT_KEY] = {
82
+ /**
83
+ * Load HTML template once and return a reusable responder
84
+ */
85
+ load(htmlPath) {
86
+ const fullPath = resolvePath(htmlPath);
87
+ const tpl = loadFile(fullPath, "html");
88
+
89
+ return (data = {}, opts = {}) => {
90
+ const finalData = injectCSS(data, opts);
91
+ const html = renderTemplate(tpl, finalData);
92
+ return t.response.html(html);
93
+ };
94
+ },
95
+
96
+ /**
97
+ * One-shot render with optional CSS
98
+ */
99
+ render(htmlPath, data = {}, opts = {}) {
100
+ const fullPath = resolvePath(htmlPath);
101
+ const tpl = loadFile(fullPath, "html");
102
+
103
+ const finalData = injectCSS(data, opts);
104
+ const html = renderTemplate(tpl, finalData);
105
+
106
+ return t.response.html(html);
107
+ },
108
+
109
+ /**
110
+ * Load CSS only (manual usage if needed)
111
+ */
112
+ css(cssPath) {
113
+ const css = loadFile(resolvePath(cssPath), "css");
114
+ return `<style>\n${css}\n</style>`;
115
+ },
116
+
117
+ /**
118
+ * Clear all cached UI files
119
+ */
120
+ clearCache() {
121
+ for (const k in memoryCache) delete memoryCache[k];
122
+
123
+ ls.keys().forEach(k => {
124
+ if (k.startsWith("html:") || k.startsWith("css:")) {
125
+ ls.remove(k);
126
+ }
127
+ });
128
+
129
+ return true;
130
+ }
131
+ };
package/jsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "es2021",
5
+ "checkJs": false,
6
+ "allowJs": true,
7
+ "moduleResolution": "node"
8
+ },
9
+ "include": [
10
+ "index.js",
11
+ "node_modules/titan-sdk/index.d.ts"
12
+ ]
13
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@t8n/ui",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight HTML templating engine for TitanPL with file caching and variable interpolation.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "test": "titan run ext"
10
+ },
11
+ "keywords": [
12
+ "titanpl",
13
+ "t8n",
14
+ "titan",
15
+ "titan planet",
16
+ "templating",
17
+ "html",
18
+ "template",
19
+ "engine",
20
+ "gravity",
21
+ "@titanpl/core",
22
+ "@ezetgalxy/titan"
23
+ ],
24
+ "author": "Shoya",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "@titanpl/core": "2.1.0",
28
+ "chokidar": "^5.0.0",
29
+ "esbuild": "^0.27.2",
30
+ "titanpl-sdk": "^1.0.6"
31
+ }
32
+ }
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ npm-debug.log
3
+ .git
@@ -0,0 +1,66 @@
1
+ # ================================================================
2
+ # STAGE 1 — Build Titan (JS → Rust)
3
+ # ================================================================
4
+ FROM rust:1.91.1 AS builder
5
+
6
+ # Install Node for Titan CLI + bundler
7
+ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
8
+ && apt-get install -y nodejs
9
+
10
+ # Install Titan CLI (latest)
11
+ RUN npm install -g @ezetgalaxy/titan@latest
12
+
13
+ WORKDIR /app
14
+
15
+ # Copy project files
16
+ COPY . .
17
+
18
+ # Install JS dependencies (needed for Titan DSL + bundler)
19
+ RUN npm install
20
+
21
+ SHELL ["/bin/bash", "-c"]
22
+
23
+ # Extract Titan extensions into .ext
24
+ RUN mkdir -p /app/.ext && \
25
+ find /app/node_modules -maxdepth 5 -type f -name "titan.json" -print0 | \
26
+ while IFS= read -r -d '' file; do \
27
+ pkg_dir="$(dirname "$file")"; \
28
+ pkg_name="$(basename "$pkg_dir")"; \
29
+ echo "Copying Titan extension: $pkg_name from $pkg_dir"; \
30
+ cp -r "$pkg_dir" "/app/.ext/$pkg_name"; \
31
+ done && \
32
+ echo "Extensions in .ext:" && \
33
+ ls -R /app/.ext
34
+
35
+ # Build Titan metadata + bundle JS actions
36
+ RUN titan build
37
+
38
+ # Build Rust binary
39
+ RUN cd server && cargo build --release
40
+
41
+
42
+
43
+ # ================================================================
44
+ # STAGE 2 — Runtime Image (Lightweight)
45
+ # ================================================================
46
+ FROM debian:stable-slim
47
+
48
+ WORKDIR /app
49
+
50
+ # Copy Rust binary from builder stage
51
+ COPY --from=builder /app/server/target/release/titan-server ./titan-server
52
+
53
+ # Copy Titan routing metadata
54
+ COPY --from=builder /app/server/routes.json ./routes.json
55
+ COPY --from=builder /app/server/action_map.json ./action_map.json
56
+
57
+ # Copy Titan JS bundles
58
+ RUN mkdir -p /app/actions
59
+ COPY --from=builder /app/server/actions /app/actions
60
+
61
+ # Copy only Titan extensions
62
+ COPY --from=builder /app/.ext ./.ext
63
+
64
+ EXPOSE 5100
65
+
66
+ CMD ["./titan-server"]
@@ -0,0 +1,7 @@
1
+ export const hello = () => {
2
+ return t.ui.render(
3
+ "static/app.html",
4
+ { name: "Titan" },
5
+ { css: "static/styles.css" }
6
+ );
7
+ };
@@ -0,0 +1,10 @@
1
+ import t from "../titan/titan.js";
2
+
3
+
4
+
5
+
6
+ t.get("/hello").action("hello") // pass a json payload { "name": "titan" }
7
+
8
+ t.get("/").reply("Ready to land on Titan Planet 🚀");
9
+
10
+ t.start(5100, "Titan Running!", 10);
@@ -0,0 +1,9 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ tpl{{ css }}
5
+ </head>
6
+ <body>
7
+ <h1>tpl{{ name }}</h1>
8
+ </body>
9
+ </html>
@@ -0,0 +1,9 @@
1
+ body{
2
+ background-color: black;
3
+ height: 100vh;
4
+ width: 100%;
5
+ }
6
+
7
+ h1{
8
+ color: rgb(32, 215, 215);
9
+ }