tux-review 0.1.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 +67 -0
- package/bin/tux.js +17 -0
- package/client/tux-review.css +155 -0
- package/client/tux-review.js +586 -0
- package/package.json +59 -0
- package/skills/tux-design-create.md +50 -0
- package/skills/tux-design-incorporate.md +61 -0
- package/skills/tux-design-install.md +56 -0
- package/skills/tux-design-start-review.md +51 -0
- package/skills/tux-design-stop-review.md +44 -0
- package/skills/tux-feedback-delete.md +45 -0
- package/skills/tux-feedback-export.md +47 -0
- package/skills/tux-feedback-show.md +55 -0
- package/skills/tux-live-create.md +50 -0
- package/skills/tux-live-incorporate.md +62 -0
- package/skills/tux-live-install.md +63 -0
- package/skills/tux-live-start-review.md +47 -0
- package/skills/tux-live-stop-review.md +46 -0
- package/src/canonical.js +28 -0
- package/src/cli.js +229 -0
- package/src/config.js +72 -0
- package/src/design.js +140 -0
- package/src/errors.js +28 -0
- package/src/feedback.js +289 -0
- package/src/ids.js +57 -0
- package/src/index.js +17 -0
- package/src/live.js +261 -0
- package/src/schema.js +77 -0
- package/src/serve-main.js +42 -0
- package/src/server.js +347 -0
- package/src/store.js +72 -0
- package/src/templates.js +44 -0
- package/templates/angular/README.md +19 -0
- package/templates/angular/angular.json +49 -0
- package/templates/angular/package.json +25 -0
- package/templates/angular/src/app/app.component.html +40 -0
- package/templates/angular/src/app/app.component.ts +30 -0
- package/templates/angular/src/index.html +11 -0
- package/templates/angular/src/main.ts +4 -0
- package/templates/angular/src/styles.css +11 -0
- package/templates/angular/tsconfig.app.json +8 -0
- package/templates/angular/tsconfig.json +11 -0
- package/templates/react/README.md +18 -0
- package/templates/react/index.html +12 -0
- package/templates/react/package.json +18 -0
- package/templates/react/src/App.jsx +80 -0
- package/templates/react/src/main.jsx +6 -0
- package/templates/react/src/styles.css +11 -0
- package/templates/vanilla/README.md +22 -0
- package/templates/vanilla/index.html +18 -0
- package/templates/vanilla/package.json +6 -0
- package/templates/vanilla/src/app.js +79 -0
- package/templates/vanilla/src/styles.css +11 -0
- package/templates/vue/README.md +18 -0
- package/templates/vue/index.html +12 -0
- package/templates/vue/package.json +17 -0
- package/templates/vue/src/App.vue +61 -0
- package/templates/vue/src/main.js +5 -0
- package/templates/vue/src/styles.css +11 -0
- package/templates/vue/vite.config.js +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# TUX Review
|
|
2
|
+
|
|
3
|
+
TUX turns a running web UI into a review artifact. Reviewers can attach
|
|
4
|
+
structured, machine-readable feedback to pages, routes, components, element
|
|
5
|
+
instances, and UI states directly in the browser.
|
|
6
|
+
|
|
7
|
+
This is the **Node.js/npm variant** of TUX. It includes the `tux` CLI, a
|
|
8
|
+
local review server, framework templates, and the framework-agnostic Review
|
|
9
|
+
Client. It has no runtime dependencies and requires Node.js 20 or newer.
|
|
10
|
+
|
|
11
|
+
Choose this package if your project uses npm/JavaScript or you need the
|
|
12
|
+
`tux-review/client` JavaScript export. If you only want a stdlib-only CLI and
|
|
13
|
+
server, install the separate but command-compatible Python package from PyPI
|
|
14
|
+
instead. Do not install both for one project.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Run the CLI without a global installation:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx --yes --package=tux-review tux --help
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install it in the project (recommended):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install --save-dev tux-review
|
|
28
|
+
npx tux --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Typical workflow
|
|
32
|
+
|
|
33
|
+
After installation, run every CLI command through `npx tux …` (or `tux …` if
|
|
34
|
+
you installed it globally). For a clickable design:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx tux design install --framework vanilla
|
|
38
|
+
npx tux design create --framework vanilla --name checkout
|
|
39
|
+
npx tux design start-review
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For an existing application, use the live-review workflow:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx tux live install
|
|
46
|
+
npx tux live start-review --url http://localhost:3000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Feedback is stored in `.tux/feedback.json` by default and can be inspected or
|
|
50
|
+
exported with `tux feedback show` and `tux feedback export`.
|
|
51
|
+
|
|
52
|
+
## What is included
|
|
53
|
+
|
|
54
|
+
- A deterministic CLI for design and live UI reviews
|
|
55
|
+
- A plain-JavaScript browser Review Client (`tux-review/client`)
|
|
56
|
+
- Vanilla, React, Vue, and Angular design templates
|
|
57
|
+
- Canonical JSON feedback and agent skills
|
|
58
|
+
|
|
59
|
+
## Documentation and support
|
|
60
|
+
|
|
61
|
+
The complete guide, examples, and normative specification are in the
|
|
62
|
+
[GitHub repository](https://github.com/sommelo1/tux). Report issues at
|
|
63
|
+
[github.com/sommelo1/tux/issues](https://github.com/sommelo1/tux/issues).
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
[MIT](https://github.com/sommelo1/tux/blob/main/LICENSE)
|
package/bin/tux.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Entry point for the `tux` binary. */
|
|
3
|
+
import { run } from '../src/cli.js';
|
|
4
|
+
|
|
5
|
+
const isTTY = process.stdin.isTTY ?? false;
|
|
6
|
+
const result = await run(process.argv.slice(2), { isTTY });
|
|
7
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
8
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
9
|
+
if (result.keepAlive) {
|
|
10
|
+
// keep the process alive while a foreground server runs
|
|
11
|
+
process.on('SIGINT', () => {
|
|
12
|
+
result.keepAlive.close();
|
|
13
|
+
process.exit(0);
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
process.exitCode = result.exit;
|
|
17
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* TUX Review Client styles — tux-owned classes only; host element
|
|
2
|
+
* selectors are never touched (Application Isolation, SPC section 61).
|
|
3
|
+
* Themeable via --tux-* tokens; values reference host variables, so host
|
|
4
|
+
* values win. Tokens live on :root because tux surfaces render in three
|
|
5
|
+
* places: the overlay (#tux-root), the top-layer dialog (portaled editor,
|
|
6
|
+
* markers, reparented float/sidebar/toast), and — for the hover outline —
|
|
7
|
+
* directly on host elements. */
|
|
8
|
+
|
|
9
|
+
:root {
|
|
10
|
+
--tux-primary: var(--primary, #569cd6);
|
|
11
|
+
--tux-surface: var(--card-bg, #fff);
|
|
12
|
+
--tux-surface2: var(--chip-bg, #f0f2f5);
|
|
13
|
+
--tux-border: var(--card-border, rgba(0, 0, 0, .12));
|
|
14
|
+
--tux-text: var(--on-surface, #1e1e1e);
|
|
15
|
+
--tux-muted: var(--outline, #6e6e6e);
|
|
16
|
+
--tux-danger: #c62828;
|
|
17
|
+
--tux-rec: #e53935;
|
|
18
|
+
--tux-green: #2e7d32;
|
|
19
|
+
--tux-yellow: #f9a825;
|
|
20
|
+
--tux-radius: 12px;
|
|
21
|
+
--tux-shadow: 0 4px 16px rgba(0, 0, 0, .16);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#tux-root {
|
|
25
|
+
font-family: system-ui, sans-serif;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
color: var(--tux-text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Floating activation surface (launcher + state label). While a native
|
|
31
|
+
<dialog> is open, the client reparents this element into the dialog so
|
|
32
|
+
it stays clickable above the backdrop. */
|
|
33
|
+
#tux-root .tux-float,
|
|
34
|
+
dialog[open] .tux-float {
|
|
35
|
+
position: fixed; right: 14px; bottom: 14px; z-index: 2147483000;
|
|
36
|
+
display: flex; align-items: center; gap: 10px;
|
|
37
|
+
font-family: system-ui, sans-serif;
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
color: var(--tux-text);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Launcher: light gray when off, recording red (pulsing) when commenting. */
|
|
43
|
+
.tux-float .tux-launcher {
|
|
44
|
+
width: 44px; height: 44px; border-radius: 50%;
|
|
45
|
+
border: 1px solid var(--tux-border); cursor: pointer;
|
|
46
|
+
background: var(--tux-surface2); color: var(--tux-muted);
|
|
47
|
+
font-size: 20px; box-shadow: var(--tux-shadow);
|
|
48
|
+
}
|
|
49
|
+
.tux-float .tux-launcher.tux-active {
|
|
50
|
+
background: var(--tux-rec); color: #fff; border-color: var(--tux-rec);
|
|
51
|
+
animation: tux-rec-pulse 1.6s ease-out infinite;
|
|
52
|
+
}
|
|
53
|
+
.tux-float .tux-launcher.tux-invite {
|
|
54
|
+
animation: tux-invite-pulse 2s ease-in-out infinite;
|
|
55
|
+
}
|
|
56
|
+
@keyframes tux-rec-pulse {
|
|
57
|
+
0% { box-shadow: 0 0 0 0 rgba(229, 57, 53, .45), var(--tux-shadow); }
|
|
58
|
+
70% { box-shadow: 0 0 0 14px rgba(229, 57, 53, 0), var(--tux-shadow); }
|
|
59
|
+
100% { box-shadow: 0 0 0 0 rgba(229, 57, 53, 0), var(--tux-shadow); }
|
|
60
|
+
}
|
|
61
|
+
@keyframes tux-invite-pulse {
|
|
62
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(86, 156, 214, .40), var(--tux-shadow); }
|
|
63
|
+
50% { box-shadow: 0 0 0 12px rgba(86, 156, 214, 0), var(--tux-shadow); }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Persistent state label next to the launcher. */
|
|
67
|
+
.tux-float .tux-launcher-hint {
|
|
68
|
+
height: 44px; display: flex; align-items: center;
|
|
69
|
+
padding: 0 14px; border-radius: 999px;
|
|
70
|
+
background: var(--tux-surface); border: 1px solid var(--tux-border);
|
|
71
|
+
box-shadow: var(--tux-shadow); color: var(--tux-text);
|
|
72
|
+
font-size: 12.5px; font-weight: 600; white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
.tux-float .tux-launcher-hint::before {
|
|
75
|
+
content: ""; width: 8px; height: 8px; border-radius: 50%;
|
|
76
|
+
background: var(--tux-muted); margin-right: 8px; flex: none;
|
|
77
|
+
}
|
|
78
|
+
.tux-float .tux-launcher-hint.tux-hint-on::before { background: var(--tux-rec); }
|
|
79
|
+
.tux-float .tux-launcher-hint.tux-hint-invite::before {
|
|
80
|
+
background: var(--tux-primary);
|
|
81
|
+
animation: tux-invite-pulse 2s ease-in-out infinite;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
body.tux-mode * { cursor: crosshair !important; }
|
|
85
|
+
|
|
86
|
+
#tux-root .tux-layer { position: absolute; left: 0; top: 0; }
|
|
87
|
+
|
|
88
|
+
/* Hover outline — lands on host elements, so this rule is intentionally
|
|
89
|
+
unscoped: the class name is tux-owned and the outline must work both on
|
|
90
|
+
plain page elements and inside top-layer dialogs. .tux-editing marks the
|
|
91
|
+
target of the currently open editor with the same outline. */
|
|
92
|
+
.tux-hover, .tux-editing { outline: 2px dashed var(--tux-primary) !important; outline-offset: -2px; }
|
|
93
|
+
|
|
94
|
+
/* Markers and the editor portal into native dialogs when the target lives
|
|
95
|
+
in the top layer — the dialog[open] alternative keeps them styled there. */
|
|
96
|
+
:is(#tux-root, dialog[open]) .tux-marker {
|
|
97
|
+
position: absolute; z-index: 2147483001;
|
|
98
|
+
min-width: 22px; height: 22px; padding: 0 4px; border-radius: 11px;
|
|
99
|
+
border: 2px solid var(--tux-surface); background: var(--tux-yellow);
|
|
100
|
+
color: #000; font-size: 11px; font-weight: 700; cursor: pointer;
|
|
101
|
+
box-shadow: var(--tux-shadow);
|
|
102
|
+
}
|
|
103
|
+
:is(#tux-root, dialog[open]) .tux-marker.tux-resolved { background: var(--tux-green); color: #fff; }
|
|
104
|
+
|
|
105
|
+
:is(#tux-root, dialog[open]) .tux-editor {
|
|
106
|
+
display: none; position: absolute; z-index: 2147483002;
|
|
107
|
+
width: 300px; padding: 12px; border-radius: var(--tux-radius);
|
|
108
|
+
border: 1px solid var(--tux-border); background: var(--tux-surface);
|
|
109
|
+
box-shadow: var(--tux-shadow); text-align: left;
|
|
110
|
+
}
|
|
111
|
+
:is(#tux-root, dialog[open]) .tux-editor.tux-open { display: block; }
|
|
112
|
+
:is(#tux-root, dialog[open]) .tux-ed-head { display: flex; justify-content: space-between; font-size: 12px; color: var(--tux-muted); margin-bottom: 6px; }
|
|
113
|
+
:is(#tux-root, dialog[open]) .tux-ed-target { font-size: 11px; color: var(--tux-muted); background: var(--tux-surface2); border-radius: 8px; padding: 6px 8px; margin-bottom: 8px; word-break: break-all; }
|
|
114
|
+
:is(#tux-root, dialog[open]) .tux-ed-row { display: flex; gap: 8px; align-items: center; font-size: 12px; margin-bottom: 8px; color: var(--tux-muted); }
|
|
115
|
+
:is(#tux-root, dialog[open]) .tux-editor select, :is(#tux-root, dialog[open]) .tux-editor textarea {
|
|
116
|
+
font: inherit; color: var(--tux-text); background: var(--tux-surface);
|
|
117
|
+
border: 1px solid var(--tux-border); border-radius: 8px; padding: 6px 8px;
|
|
118
|
+
}
|
|
119
|
+
:is(#tux-root, dialog[open]) .tux-editor textarea { width: 100%; min-height: 70px; box-sizing: border-box; resize: vertical; }
|
|
120
|
+
:is(#tux-root, dialog[open]) .tux-ed-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
|
|
121
|
+
:is(#tux-root, dialog[open]) .tux-btn-primary { background: var(--tux-primary); color: #fff; border: none; border-radius: 999px; padding: 6px 14px; cursor: pointer; }
|
|
122
|
+
:is(#tux-root, dialog[open]) .tux-btn-ghost { background: none; border: 1px solid var(--tux-border); color: var(--tux-muted); border-radius: 999px; padding: 6px 14px; cursor: pointer; }
|
|
123
|
+
:is(#tux-root, dialog[open]) .tux-btn-danger { background: none; border: 1px solid var(--tux-danger); color: var(--tux-danger); border-radius: 999px; padding: 6px 14px; cursor: pointer; }
|
|
124
|
+
:is(#tux-root, dialog[open]) .tux-x { background: none; border: none; color: var(--tux-muted); cursor: pointer; }
|
|
125
|
+
|
|
126
|
+
/* Sidebar and toast can be reparented into an open dialog together with
|
|
127
|
+
the float — the dialog[open] alternative keeps them styled there. */
|
|
128
|
+
:is(#tux-root, dialog[open]) .tux-sidebar {
|
|
129
|
+
position: fixed; right: 0; top: 0; bottom: 0; z-index: 2147482999;
|
|
130
|
+
width: 300px; background: var(--tux-surface); border-left: 1px solid var(--tux-border);
|
|
131
|
+
box-shadow: var(--tux-shadow); transform: translateX(100%); transition: transform .18s ease;
|
|
132
|
+
display: flex; flex-direction: column;
|
|
133
|
+
}
|
|
134
|
+
:is(#tux-root, dialog[open]) .tux-sidebar.tux-open { transform: translateX(0); }
|
|
135
|
+
:is(#tux-root, dialog[open]) .tux-sb-head { display: flex; justify-content: space-between; align-items: center; padding: 12px 14px; font-weight: 600; border-bottom: 1px solid var(--tux-border); }
|
|
136
|
+
:is(#tux-root, dialog[open]) .tux-sb-close { background: none; border: none; color: var(--tux-muted); cursor: pointer; }
|
|
137
|
+
:is(#tux-root, dialog[open]) .tux-sb-meta { padding: 8px 14px; font-size: 11px; color: var(--tux-muted); border-bottom: 1px solid var(--tux-border); }
|
|
138
|
+
:is(#tux-root, dialog[open]) .tux-sb-list { overflow: auto; flex: 1; padding: 8px; }
|
|
139
|
+
:is(#tux-root, dialog[open]) .tux-sb-item { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; padding: 8px; border-radius: 10px; background: var(--tux-surface2); margin-bottom: 8px; cursor: pointer; }
|
|
140
|
+
:is(#tux-root, dialog[open]) .tux-sb-item.tux-done { opacity: .55; }
|
|
141
|
+
:is(#tux-root, dialog[open]) .tux-sb-type { grid-row: span 2; align-self: center; font-size: 10px; text-transform: uppercase; padding: 3px 7px; border-radius: 999px; background: var(--tux-yellow); }
|
|
142
|
+
:is(#tux-root, dialog[open]) .tux-type-approval { background: var(--tux-green); color: #fff; }
|
|
143
|
+
:is(#tux-root, dialog[open]) .tux-type-question { background: var(--tux-primary); color: #fff; }
|
|
144
|
+
:is(#tux-root, dialog[open]) .tux-type-issue { background: var(--tux-danger); color: #fff; }
|
|
145
|
+
:is(#tux-root, dialog[open]) .tux-sb-text { font-size: 12px; line-height: 1.4; word-break: break-word; }
|
|
146
|
+
:is(#tux-root, dialog[open]) .tux-sb-route { font-size: 10px; color: var(--tux-muted); }
|
|
147
|
+
:is(#tux-root, dialog[open]) .tux-sb-empty { color: var(--tux-muted); font-size: 12px; padding: 12px; }
|
|
148
|
+
|
|
149
|
+
:is(#tux-root, dialog[open]) .tux-toast {
|
|
150
|
+
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%) translateY(20px);
|
|
151
|
+
z-index: 2147483003; background: var(--tux-text); color: var(--tux-surface);
|
|
152
|
+
padding: 8px 16px; border-radius: 999px; font-size: 12px; opacity: 0;
|
|
153
|
+
transition: opacity .15s ease, transform .15s ease; pointer-events: none;
|
|
154
|
+
}
|
|
155
|
+
:is(#tux-root, dialog[open]) .tux-toast.tux-visible { opacity: 1; transform: translateX(-50%) translateY(0); }
|