Skip to content

Commit 8f21055

Browse files
committed
Add admin-only notes viewer with bracket subnotes - PR_26156_184-admin-notes-viewer
1 parent 2e39dad commit 8f21055

12 files changed

Lines changed: 465 additions & 45 deletions

File tree

admin/notes.html

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<base href="/">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>Admin Notes - GameFoundryStudio</title>
9+
<meta name="description" content="Admin-only project notes viewer for GameFoundryStudio planning notes.">
10+
<link rel="icon" href="/favicon.svg">
11+
<link rel="stylesheet" href="assets/theme-v2/css/theme.css">
12+
</head>
13+
14+
<body>
15+
<div data-partial="header-nav"></div>
16+
<main data-admin-only="true">
17+
<section class="page-title">
18+
<div class="container">
19+
<div class="kicker">Admin Only</div>
20+
<h1>Admin Notes</h1>
21+
<p class="lede">Private project notes for ideas, fixes, and undecided questions.</p>
22+
</div>
23+
</section>
24+
<section class="section">
25+
<div class="container account-panel">
26+
<aside class="side-menu" aria-label="Admin pages">
27+
<a href="admin/analytics.html">Analytics</a>
28+
<a href="admin/branding.html">Branding</a>
29+
<a href="admin/controls.html">Controls</a>
30+
<a href="admin/design-system.html">Design System</a>
31+
<a href="toolbox/environments/index.html">Environments</a>
32+
<a href="toolbox/game-migration/index.html">Game Migration</a>
33+
<a href="admin/grouping-colors.html">Grouping Colors</a>
34+
<a href="admin/moderation.html">Moderation</a>
35+
<a class="active" aria-current="page" href="admin/notes.html">Notes</a>
36+
<a href="toolbox/platform-settings/index.html">Platform Settings</a>
37+
<a href="admin/ratings.html">Ratings</a>
38+
<a href="admin/roles.html">Roles</a>
39+
<a href="admin/site-settings.html">Site Settings</a>
40+
<a href="admin/themes.html">Themes</a>
41+
<a href="admin/tools-progress.html">Tools Progress</a>
42+
<a href="admin/users.html">Users</a>
43+
</aside>
44+
<article class="card" aria-labelledby="admin-notes-title">
45+
<div class="card-body content-stack">
46+
<div>
47+
<div class="kicker">Project Notes</div>
48+
<h2 id="admin-notes-title" data-admin-notes-title>note.txt</h2>
49+
</div>
50+
<a href="admin/notes.html" data-admin-notes-return hidden>Return to note.txt</a>
51+
<div class="status" role="status" data-admin-notes-status>Loading admin notes.</div>
52+
<div class="content-stack" data-admin-notes-content aria-live="polite"></div>
53+
</div>
54+
</article>
55+
</div>
56+
</section>
57+
</main>
58+
<div data-partial="footer"></div>
59+
<script src="assets/theme-v2/js/gamefoundry-partials.js" defer></script>
60+
<script type="module" src="admin/notes.js"></script>
61+
</body>
62+
63+
</html>

admin/notes.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
const NOTES_DIRECTORY = "docs_build/dev/admin-notes";
2+
const DEFAULT_NOTE = "note";
3+
const NOTE_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
4+
5+
class AdminNotesViewer {
6+
constructor(documentRef = document, historyRef = window.history) {
7+
this.documentRef = documentRef;
8+
this.historyRef = historyRef;
9+
this.title = documentRef.querySelector("[data-admin-notes-title]");
10+
this.returnLink = documentRef.querySelector("[data-admin-notes-return]");
11+
this.status = documentRef.querySelector("[data-admin-notes-status]");
12+
this.content = documentRef.querySelector("[data-admin-notes-content]");
13+
this.bindEvents();
14+
}
15+
16+
bindEvents() {
17+
this.content?.addEventListener("click", (event) => {
18+
const link = event.target.closest("[data-admin-note-link]");
19+
if (!link) {
20+
return;
21+
}
22+
event.preventDefault();
23+
this.openNote(link.dataset.adminNoteLink || DEFAULT_NOTE, true);
24+
});
25+
26+
this.returnLink?.addEventListener("click", (event) => {
27+
event.preventDefault();
28+
this.openNote(DEFAULT_NOTE, true);
29+
});
30+
}
31+
32+
start() {
33+
this.openNote(this.noteNameFromLocation(window.location), false);
34+
}
35+
36+
noteNameFromLocation(locationRef) {
37+
const params = new URLSearchParams(locationRef.search);
38+
return this.noteNameFromValue(params.get("note") || DEFAULT_NOTE);
39+
}
40+
41+
noteNameFromValue(value) {
42+
return String(value || DEFAULT_NOTE).replace(/\.txt$/i, "");
43+
}
44+
45+
validNoteName(noteName) {
46+
return NOTE_NAME_PATTERN.test(noteName);
47+
}
48+
49+
notePath(noteName) {
50+
return `${NOTES_DIRECTORY}/${noteName}.txt`;
51+
}
52+
53+
async openNote(noteName, pushHistory) {
54+
const normalizedNoteName = this.noteNameFromValue(noteName);
55+
this.clearContent();
56+
this.setTitle(`${normalizedNoteName || DEFAULT_NOTE}.txt`);
57+
this.setReturnVisible(normalizedNoteName !== DEFAULT_NOTE);
58+
59+
if (!this.validNoteName(normalizedNoteName)) {
60+
this.renderError(
61+
`Rejected note path "${noteName}". Use a bracket note name containing only letters, numbers, underscores, or hyphens.`
62+
);
63+
return;
64+
}
65+
66+
const notePath = this.notePath(normalizedNoteName);
67+
try {
68+
const response = await fetch(notePath, { cache: "no-store" });
69+
if (!response.ok) {
70+
this.renderError(`Missing note file ${notePath}. Add this file under ${NOTES_DIRECTORY}/ or return to note.txt.`);
71+
return;
72+
}
73+
const text = await response.text();
74+
this.renderNote(text, normalizedNoteName);
75+
this.setStatus(`Loaded ${notePath}.`);
76+
if (pushHistory) {
77+
this.historyRef.pushState({}, "", this.hrefForNote(normalizedNoteName));
78+
}
79+
} catch {
80+
this.renderError(`Unable to load ${notePath}. Check the note file and reload the page.`);
81+
}
82+
}
83+
84+
hrefForNote(noteName) {
85+
return noteName === DEFAULT_NOTE ? "admin/notes.html" : `admin/notes.html?note=${encodeURIComponent(noteName)}`;
86+
}
87+
88+
clearContent() {
89+
this.content?.replaceChildren();
90+
}
91+
92+
setTitle(value) {
93+
if (this.title) {
94+
this.title.textContent = value;
95+
}
96+
}
97+
98+
setStatus(value) {
99+
if (this.status) {
100+
this.status.textContent = value;
101+
}
102+
}
103+
104+
setReturnVisible(visible) {
105+
if (this.returnLink) {
106+
this.returnLink.hidden = !visible;
107+
}
108+
}
109+
110+
renderError(message) {
111+
this.setStatus("Admin note error.");
112+
const errorMessage = this.documentRef.createElement("p");
113+
errorMessage.className = "status";
114+
errorMessage.dataset.adminNotesError = "";
115+
errorMessage.textContent = message;
116+
this.content?.append(errorMessage);
117+
}
118+
119+
renderNote(text, noteName) {
120+
this.setTitle(`${noteName}.txt`);
121+
const lines = text.split(/\r?\n/);
122+
lines.forEach((line) => {
123+
const trimmed = line.trim();
124+
if (!trimmed) {
125+
return;
126+
}
127+
const node = this.sectionHeading(trimmed)
128+
? this.documentRef.createElement("h3")
129+
: this.documentRef.createElement("p");
130+
this.appendLinkedText(node, trimmed);
131+
this.content?.append(node);
132+
});
133+
}
134+
135+
sectionHeading(text) {
136+
return ["Ideas", "Things to Fix", "Undecided Questions"].includes(text);
137+
}
138+
139+
appendLinkedText(parent, text) {
140+
const bracketPattern = /\[([^\]]+)\]/g;
141+
let cursor = 0;
142+
let match = bracketPattern.exec(text);
143+
while (match) {
144+
parent.append(this.documentRef.createTextNode(text.slice(cursor, match.index)));
145+
parent.append(this.linkOrText(match[1]));
146+
cursor = match.index + match[0].length;
147+
match = bracketPattern.exec(text);
148+
}
149+
parent.append(this.documentRef.createTextNode(text.slice(cursor)));
150+
}
151+
152+
linkOrText(noteName) {
153+
const normalizedNoteName = this.noteNameFromValue(noteName);
154+
if (!this.validNoteName(normalizedNoteName)) {
155+
return this.documentRef.createTextNode(`[${noteName}]`);
156+
}
157+
const link = this.documentRef.createElement("a");
158+
link.href = this.hrefForNote(normalizedNoteName);
159+
link.dataset.adminNoteLink = normalizedNoteName;
160+
link.textContent = `[${noteName}]`;
161+
return link;
162+
}
163+
}
164+
165+
new AdminNotesViewer().start();

admin/notes/note.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[ ] Tiles
2+
-
3+
4+
[ ] Project Life Cycle
5+
6+
7+
[ ] Palette updates
8+
- tone (earthy, human, water, floral, TV Age, console age, arcade)
9+
10+
[ ] Picker
11+
- left to right
12+
- Middle Gray ROYGBIV
13+
- step sizes (2-255)
14+
15+
[ ] Project dificullty
16+
- left to right easiest to hardest
17+
- build off of, type, enemy, heros, levels, etc.
18+
19+
[x] Admin notes
20+
- reads txt file (this one) and displays
21+
- if a note has [subnote] it should be a link on the page and display [subnote].txt
22+
- example [other] should display other.txt,
23+
- at the top of subnote pages, have a return take you to note.txt
24+
- name is up for discuttion (this is my ideas, things I want to fix, undecided question)
25+
[ ] Correction Notes update
26+
[subnote] should be /[subnote]/note.txt
27+
28+
29+
[ ] Test/Debug Performance
30+
- use the game engine hooks to track performance
31+
- each section of the loop(draw, physics, move, etc)
32+
- each section will have a start end time
33+
- The test/debug page will be the location for test,
34+
- only on when run from test

admin/notes/other/note.txt

Whitespace-only changes.

assets/theme-v2/js/gamefoundry-partials.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"admin-users": "admin/users.html",
7878
"admin-roles": "admin/roles.html",
7979
"admin-moderation": "admin/moderation.html",
80+
"admin-notes": "admin/notes.html",
8081
"admin-analytics": "admin/analytics.html",
8182
"admin-tools-progress": "admin/tools-progress.html",
8283
"cookie-policy": "legal/cookie-policy.html",

assets/theme-v2/partials/header-nav.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
<a data-nav-link data-route="game-migration" href="toolbox/game-migration/index.html">Game Migration</a>
118118
<a data-nav-link data-route="admin-grouping-colors" href="admin/grouping-colors.html">Grouping Colors</a>
119119
<a data-nav-link data-route="admin-moderation" href="admin/moderation.html">Moderation</a>
120+
<a data-nav-link data-route="admin-notes" href="admin/notes.html">Notes</a>
120121
<a data-nav-link data-route="platform-settings" href="toolbox/platform-settings/index.html">Platform Settings</a>
121122
<a data-nav-link data-route="admin-ratings" href="admin/ratings.html">Ratings</a>
122123
<a data-nav-link data-route="admin-roles" href="admin/roles.html">Roles</a>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Ideas
2+
- Capture admin-only project ideas here.
3+
- Use bracket links for connected notes, such as [other].
4+
5+
Things to Fix
6+
- Replace placeholder notes with concrete admin follow-up items.
7+
- Track fixes that are not ready for a dedicated BUILD_PR yet.
8+
9+
Undecided Questions
10+
- Decide which project questions need their own subnote files.
11+
- Keep open decisions visible until they become scoped work.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Other
2+
- This is a sample linked admin subnote.
3+
- Use Return to note.txt to get back to the main admin note.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# PR_26156_184 Admin Notes Viewer Report
2+
3+
## Result
4+
PASS
5+
6+
## Summary
7+
- Added `admin/notes.html` as an Admin-only Theme V2 page for project notes.
8+
- Added `admin/notes.js` to load notes from `docs_build/dev/admin-notes/`, render bracket note links, show subnote return navigation, and reject unsafe note names before fetch.
9+
- Added `docs_build/dev/admin-notes/note.txt` with placeholder sections for Ideas, Things to Fix, and Undecided Questions.
10+
- Added `docs_build/dev/admin-notes/other.txt` as the linked subnote used by the targeted validation lane.
11+
- Registered Admin Notes in Theme V2 Admin navigation and route mapping.
12+
13+
## Scope Controls
14+
- No archived V1/V2 files changed.
15+
- No `start_of_day` files changed.
16+
- No page-local CSS, inline styles, `<style>` blocks, inline event handlers, or inline `<script>` blocks added.
17+
- The pre-existing untracked `admin/notes/` scratch folder from an earlier failed run was left untouched and excluded from PR artifacts.
18+
19+
## Runtime Behavior Verified
20+
- `note.txt` displays on `admin/notes.html`: PASS.
21+
- `[other]` renders as a link and opens `docs_build/dev/admin-notes/other.txt`: PASS.
22+
- Subnote view shows and follows `Return to note.txt`: PASS.
23+
- Missing subnote shows a visible actionable error: PASS.
24+
- Path traversal query attempts are rejected before fetching: PASS.
25+
- Header Admin navigation includes Admin Notes: PASS.
26+
- Toolbox menu does not register Admin Notes as a public tool: PASS.
27+
28+
## Notes
29+
- The missing-note validation intentionally requests a non-existent file and observes the expected browser 404 while verifying the visible actionable error.
30+
- Admin-only enforcement follows the existing static Admin surface pattern: Admin directory route, Admin navigation registration, and Admin-only page marking.

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,22 @@ Note: entry percentages use function coverage when available, otherwise line cov
1212
Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran.
1313

1414
Exercised tool entry points detected:
15-
(88%) Toolbox Index - exercised 5 runtime JS files
15+
(0%) Toolbox Index - not exercised by this Playwright run
1616
(0%) Tool Template V2 - not exercised by this Playwright run
17-
(83%) Theme V2 Shared JS - exercised 2 runtime JS files
17+
(80%) Theme V2 Shared JS - exercised 1 runtime JS files
1818

1919
Changed runtime JS files covered:
20-
(90%) toolbox/colors/palette-workspace-repository.js - executed lines 1288/1288; executed functions 129/144
21-
(97%) toolbox/colors/colors.js - executed lines 883/883; executed functions 88/91
20+
(80%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 206/206; executed functions 16/20
21+
(100%) admin/notes.js - executed lines 143/143; executed functions 22/22
2222

2323
Files with executed line/function counts where available:
24-
(65%) toolbox/project-workspace/project-workspace-mock-repository.js - executed lines 402/402; executed functions 20/31
25-
(75%) toolbox/toolRegistry.js - executed lines 1754/1754; executed functions 27/36
26-
(80%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 205/205; executed functions 16/20
27-
(87%) assets/theme-v2/js/tool-display-mode.js - executed lines 201/201; executed functions 13/15
28-
(90%) toolbox/colors/palette-workspace-repository.js - executed lines 1288/1288; executed functions 129/144
29-
(97%) toolbox/colors/colors.js - executed lines 883/883; executed functions 88/91
30-
(100%) toolbox/colors/palette-source-mock-db.js - executed lines 927/927; executed functions 6/6
24+
(80%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 206/206; executed functions 16/20
25+
(100%) admin/notes.js - executed lines 143/143; executed functions 22/22
3126

3227
Uncovered or low-coverage changed JS files:
3328
(100%) none - no low-coverage changed runtime JS files
3429

3530
Changed JS files considered:
36-
(0%) tests/helpers/toolFormControlAssertions.mjs - changed JS file not collected as browser runtime coverage
37-
(0%) tests/playwright/tools/PaletteToolMockRepository.spec.mjs - changed JS file not collected as browser runtime coverage
38-
(90%) toolbox/colors/palette-workspace-repository.js - changed JS file with browser V8 coverage
39-
(97%) toolbox/colors/colors.js - changed JS file with browser V8 coverage
31+
(0%) tests/playwright/tools/AdminNotesViewer.spec.mjs - changed JS file not collected as browser runtime coverage
32+
(80%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage
33+
(100%) admin/notes.js - changed JS file with browser V8 coverage

0 commit comments

Comments
 (0)