Skip to content

Commit bb417b9

Browse files
committed
feat(output): pure four-act model for the output tab
1 parent 2a4fce3 commit bb417b9

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

output-acts.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// output-acts.js
2+
// Pure act model for the output tab. Groups the existing tab indices (the #t0..#t27
3+
// panels) into four ordered "acts" — Decide, Understand, Go Deeper, Act — for a
4+
// two-tier nav. No DOM, no network, no imports: this is the testable contract the
5+
// nav rendering and show() build on. Tab indices and slugs are unchanged from the
6+
// existing TAB_SLUGS, so deep links and per-repo recall keep working.
7+
8+
export const ACTS = [
9+
{ id: 'decide', label: 'Decide', tabs: [9] },
10+
{ id: 'understand', label: 'Understand', tabs: [0, 1, 2, 3, 4, 5, 8, 6, 7, 15] },
11+
{ id: 'deeper', label: 'Go Deeper', tabs: [10, 27, 11, 12, 13, 14, 21, 22, 23, 24] },
12+
{ id: 'act', label: 'Act', tabs: [25, 16, 17, 18, 19, 20, 26] },
13+
];
14+
15+
export const ACT_ORDER = ACTS.map((a) => a.id);
16+
17+
// Tab index → human label (mirrors the current nav button text; used to render
18+
// the secondary row from the model instead of hardcoded HTML).
19+
export const TAB_LABELS = {
20+
9: 'Verdict',
21+
0: 'ELI5',
22+
1: 'Technical',
23+
2: 'Use Cases',
24+
3: 'Skip If',
25+
4: 'Enables',
26+
5: 'Pros / Cons',
27+
8: 'Red Flags',
28+
6: 'Alternatives',
29+
7: 'Health',
30+
15: 'Tech Stack',
31+
10: 'Deep Dive',
32+
27: 'Canvas',
33+
11: 'Systems',
34+
12: 'Ideate',
35+
13: 'Prioritize',
36+
14: 'SKTPG',
37+
21: 'Docs Quality',
38+
22: 'Maintenance',
39+
23: 'License',
40+
24: 'Since Last Scan',
41+
25: 'Fits MY Stack?',
42+
16: 'Similar',
43+
17: 'Versus',
44+
18: 'Synergies',
45+
19: 'Connections',
46+
20: 'Combine',
47+
26: 'Ask',
48+
};
49+
50+
const ACT_BY_TAB = (() => {
51+
const m = {};
52+
for (const a of ACTS) for (const t of a.tabs) m[t] = a.id;
53+
return m;
54+
})();
55+
56+
/** @returns {string|null} the act id owning tab `n`, or null. */
57+
export function actForTab(n) {
58+
return Object.prototype.hasOwnProperty.call(ACT_BY_TAB, n) ? ACT_BY_TAB[n] : null;
59+
}
60+
61+
/** @returns {number[]} the tab indices in an act, in display order. */
62+
export function tabsForAct(id) {
63+
const a = ACTS.find((x) => x.id === id);
64+
return a ? a.tabs : [];
65+
}

tests/output-acts.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { ACTS, ACT_ORDER, TAB_LABELS, actForTab, tabsForAct } from '../output-acts.js';
3+
4+
describe('act model', () => {
5+
it('orders the four acts', () => {
6+
expect(ACT_ORDER).toEqual(['decide', 'understand', 'deeper', 'act']);
7+
});
8+
9+
it('covers every tab index 0..27 exactly once', () => {
10+
const all = ACTS.flatMap((a) => a.tabs).sort((x, y) => x - y);
11+
expect(all).toEqual(Array.from({ length: 28 }, (_, i) => i));
12+
expect(new Set(all).size).toBe(28); // no duplicates
13+
});
14+
15+
it('maps a tab to its owning act', () => {
16+
expect(actForTab(9)).toBe('decide');
17+
expect(actForTab(7)).toBe('understand'); // Health
18+
expect(actForTab(10)).toBe('deeper'); // Deep Dive
19+
expect(actForTab(17)).toBe('act'); // Versus
20+
expect(actForTab(99)).toBeNull();
21+
});
22+
23+
it('lists tabs for an act in display order', () => {
24+
expect(tabsForAct('decide')).toEqual([9]);
25+
expect(tabsForAct('understand')[0]).toBe(0); // ELI5 leads
26+
expect(tabsForAct('nope')).toEqual([]);
27+
});
28+
29+
it('has a label for every tab it groups', () => {
30+
for (const t of ACTS.flatMap((a) => a.tabs)) {
31+
expect(typeof TAB_LABELS[t]).toBe('string');
32+
expect(TAB_LABELS[t].length).toBeGreaterThan(0);
33+
}
34+
});
35+
});

0 commit comments

Comments
 (0)