@kenjura/ursa 0.75.0 → 0.77.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/meta/default.css +149 -3
  3. package/meta/templates/default-template/default.css +1268 -0
  4. package/meta/{default-template.html → templates/default-template/index.html} +49 -2
  5. package/meta/{menu.js → templates/default-template/menu.js} +1 -1
  6. package/meta/templates/default-template/sectionify.js +46 -0
  7. package/meta/templates/default-template/widgets.js +701 -0
  8. package/package.json +1 -1
  9. package/src/dev.js +125 -34
  10. package/src/helper/assetBundler.js +471 -0
  11. package/src/helper/build/autoIndex.js +26 -23
  12. package/src/helper/build/cacheBust.js +79 -0
  13. package/src/helper/build/navCache.js +4 -0
  14. package/src/helper/build/templates.js +176 -19
  15. package/src/helper/build/watchCache.js +7 -0
  16. package/src/helper/customMenu.js +4 -2
  17. package/src/helper/dependencyTracker.js +269 -0
  18. package/src/helper/findScriptJs.js +29 -0
  19. package/src/helper/findStyleCss.js +29 -0
  20. package/src/helper/portUtils.js +132 -0
  21. package/src/jobs/generate.js +276 -59
  22. package/src/serve.js +446 -162
  23. package/meta/character-sheet.css +0 -50
  24. package/meta/widgets.js +0 -376
  25. /package/meta/{goudy_bookletter_1911-webfont.woff → shared/goudy_bookletter_1911-webfont.woff} +0 -0
  26. /package/meta/{character-sheet/css → templates/character-sheet-template}/character-sheet.css +0 -0
  27. /package/meta/{character-sheet/js → templates/character-sheet-template}/components.js +0 -0
  28. /package/meta/{cssui.bundle.min.css → templates/character-sheet-template/cssui.bundle.min.css} +0 -0
  29. /package/meta/{character-sheet-template.html → templates/character-sheet-template/index.html} +0 -0
  30. /package/meta/{character-sheet/js → templates/character-sheet-template}/main.js +0 -0
  31. /package/meta/{character-sheet/js → templates/character-sheet-template}/model.js +0 -0
  32. /package/meta/{search.js → templates/default-template/search.js} +0 -0
  33. /package/meta/{sticky.js → templates/default-template/sticky.js} +0 -0
  34. /package/meta/{toc-generator.js → templates/default-template/toc-generator.js} +0 -0
  35. /package/meta/{toc.js → templates/default-template/toc.js} +0 -0
  36. /package/meta/{template2.html → templates/template2/index.html} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,88 @@
1
+ # 0.77.0
2
+ 2026-02-13
3
+
4
+ QOL:
5
+ - When 'serve' encounters an occupied port 8080, prompt the user to find an available port instead of just exiting with an error. Will check open ports and find the closest port to 8080, then ask the user if they want to use it.
6
+
7
+ Meta cleanup:
8
+ - Templates should have their own folder, including default
9
+ - All static files for a template should be in the template's folder, and probably in the right subfolder (e.g. public/default.css should be in templates/default/public/default.css or something like that).
10
+ - Template filenames should be templates/{templateName}/index.html
11
+ - Ursa should throw a warning if it finds orphaned static files in meta that aren't referenced by a template
12
+
13
+ Static assets revamp:
14
+ - Revamped the building of static assets (stylesheets and scripts). The new logic is:
15
+ - All meta scripts and stylesheets should be bundled together into a single CSS file and a single JS file for the entire site. This applies to build mode; in dev mode, they are served individually for easier debugging and regeneration.
16
+ - Bundle-able document files (style.css, script.js, menu.md) will be bundled together on a per-folder basis. Each document will include the bundles from its own folder and all parent folders (where they exist).
17
+
18
+ Regeneration revamp:
19
+ - Existing logic:
20
+ - On first generation, save a cache of document output given some sort of hash of the source file and metadata (e.g. mtime, size, etc.)
21
+ - All static files (meta and document) should include a datetime or hash-based cache-buster in their query strings / filenames, so they can be invalidated as needed
22
+ - On subsequent generations, if the source file's hash is unchanged, skip regeneration and reuse the existing output file. (Note: this doesn't handle cases where the statis files changed and the document didn't; see below)
23
+ - Push a notification to the client when a file is regenerated, so the client can update the page if it's currently being viewed
24
+ - New logic is as above, plus: (some of this is partially complete, but these are the complete requirements)
25
+ - When any file being watched is changed, determine the list of affected files. For instance:
26
+ - A normal document will obviously invalidate that exact document.
27
+ - Special Ursa static files (menu.md, style.css, and script.js) are inherited by all documents in the current folder and all subfolders, so they will invalidate all documents in the current folder and all subfolders.
28
+ - Meta static files:
29
+ - A template file in meta will invalidate all documents that use that template.
30
+ - A stylesheet or script file in meta will invalidate all documents that inherit from that meta (which is probably everything).
31
+ - All other static files in the docroot (assuming they're linked at all by any document) should be invalidated thus:
32
+ - Calculate a new hash for the static file
33
+ - Find all documents that reference that static file
34
+ - Regenerate the html (even if the source md/mdx/txt file is unchanged) for those documents to update the cache-busting query string for the static file reference
35
+ - This should catch all the various edge cases that previously required restarting the server or doing a full regeneration.
36
+ - Regeneration priority order:
37
+ - When regeneration is triggered, check for connected WebSocket clients and get their current URL.
38
+ - If their current URL is affected by the change (document, static asset, template, anything), prioritize the necessary documents and assets to serve that URL before all others, and when they are regenerated, send the push notification to reload.
39
+ - After that, regenerate the rest of the affected documents in the background.
40
+ - In cases of rapid changing of files, do the following:
41
+ - Debounce all file system change events within a short time window (e.g. 500ms); wait for at least 500ms of no changes before starting regeneration. This helps in cases where a script or bot is making many changes to many files.
42
+ - When a change is detected, before any complex processing, send a ping to WebSocket-connected clients that an update is in progress, but it isn't known yet if it will affect their page.
43
+ - When it is determined that a change will affect the current page of a connected client, send another push to let the client know.
44
+ - Handle the above two notifications in the UI thus:
45
+ - When updates start, add a subtle loading indicator to the right of all left widgets, such as <Loader color="gray" />
46
+ - If it turns out the update doesn't need a refresh, remove the indicator.
47
+ - If it does need a refresh, change the indicator to a <Loader color="green" />.
48
+ - When the hot refresh actually happens, the indicator shouldn't be there anymore.
49
+ - Cache changes (under consideration):
50
+ - Before the cache was implemented, every file change triggered a complete regeneration of the entire site. This was slow, obviously. First, an in-memory cache was added to speed up the regeneration of unchanged files. But since the watcher back then missed a lot of regeneration cases (such as all meta changes), killing 'serve' and restarting it was quite common. Thus, the cache was persisted to disk, so that even in the case of a full regeneration (such as after a restart), unchanged files would still be skipped. This has worked fairly well, but for the invalidation edge cases described above (already fixed).
51
+ - However, considering that we now have a robust regeneration system that can handle all edge cases and push updates to the client, we may want to consider removing the cache entirely. The cache adds complexity and can sometimes get into a bad state, requiring manual deletion. With the new regeneration system, we could keep the cache in-memory, and rarely will the user need to kill 'serve' and restart just to get an update (ideally, never). In-memory cache is even faster than disk, so this might be a better experience overall.
52
+ - Regeneration cases still unhandled:
53
+ - User updates Ursa itself (e.g. npm update) while serve is active. I mean, this shouldn't be very common outside of Ursa devs, but's determine:
54
+ - Does the current system actually catch the meta changes?
55
+
56
+
57
+
58
+ Top Menu improvements:
59
+ - When a submenu overflows the available viewport height, it should become scrollable instead of overflowing off the screen. This can be achieved with CSS by setting a max-height and overflow-y: auto on the submenu container.
60
+
61
+ New Widgets:
62
+ - Suggested Content
63
+ - A new left-side widget that shows a list of suggested content based on the current page. Categories of suggested content:
64
+ - Content you frequently view (uses localStorage to track page views and show most viewed content)
65
+ - Future ideas:
66
+ - LLM-guided suggestions based on frequently viewed content, suggested related documents you haven't viewed yet, etc.
67
+
68
+ Bugs:
69
+ - [x] When using menu.md with auto-generation, the top menu's Home href is "//index.html" instead of "/index.html". On localhost, this ends up working fine, but on https://realdomain.com, this loads https://index.html which obviously doesn't work. The current logic seems to prefer absolute URLs, so in this case, the url for home should be "/index.html" (not double slash).
70
+ - [x] Site style.css is not present on auto-generated index pages
71
+ - Regeneration issues:
72
+ - Create a power, that power page now exists. But powers.json doesn't have it.
73
+
74
+ # 0.76.0
75
+ 2026-02-11
76
+
77
+ - **New Feature: Recent Activity widget.** A new topbar widget shows the 10 most recently modified documents in the docroot, sorted by modification date (most recent first). The widget appears on the left side of the top nav (to the right of the home icon) and is open by default.
78
+ - Recent activity data is collected during the generate phase by stat-ing each article file, then written to `public/recent-activity.json`.
79
+ - In serve/dev mode, the recent activity list is built during background cache initialization and updated live when article files are changed.
80
+ - The single-file regeneration path (`regenerateSingleFile`) also updates the recent activity JSON incrementally.
81
+ - **Widget system improvements:**
82
+ - All widgets now have a close (✕) button in the upper-right corner of their panel header. Clicking it closes the widget and deselects the corresponding icon in the top bar.
83
+ - Widget open/closed state is now persisted in localStorage. Widgets that were open will remain open after a page reload, and widgets that were closed will remain closed. Widgets with no saved state fall back to their default (Recent Activity defaults to open; others default to closed).
84
+ - The widget system now supports both left-side and right-side widget panels, which operate independently (one widget per side can be open at a time).
85
+
1
86
  # 0.75.0
2
87
  2026-02-10
3
88
 
package/meta/default.css CHANGED
@@ -72,12 +72,19 @@ nav#nav-global {
72
72
  background: none;
73
73
  border: none;
74
74
  cursor: pointer;
75
- justify-self: start;
76
75
  }
77
76
  button.menu-button:hover {
78
77
  opacity: 0.7;
79
78
  }
80
79
 
80
+ /* Left controls: menu button + left-side widgets */
81
+ .nav-left-controls {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-self: start;
85
+ gap: 0;
86
+ }
87
+
81
88
  /* Center container for search and top menu */
82
89
  .nav-center {
83
90
  width: calc(var(--article-width) + 38px);
@@ -128,7 +135,7 @@ nav#nav-global {
128
135
 
129
136
  /* ==========================================
130
137
  WIDGET SYSTEM STYLES
131
- Right-side nav widgets (TOC, Search, Profile)
138
+ Nav widgets (TOC, Search, Profile, Recent Activity)
132
139
  ========================================== */
133
140
 
134
141
  /* Widget bar in the nav right column */
@@ -145,6 +152,35 @@ nav#nav-global .nav-right-controls {
145
152
  gap: 0;
146
153
  }
147
154
 
155
+ /* Ursa update indicator (loading spinner in nav bar) */
156
+ .ursa-update-indicator {
157
+ display: none;
158
+ align-items: center;
159
+ justify-content: center;
160
+ width: var(--global-nav-height);
161
+ height: var(--global-nav-height);
162
+ opacity: 0.7;
163
+ }
164
+ .ursa-spinner {
165
+ width: 14px;
166
+ height: 14px;
167
+ border: 2px solid rgba(128, 128, 128, 0.3);
168
+ border-top-color: rgba(128, 128, 128, 0.8);
169
+ border-radius: 50%;
170
+ animation: ursa-spin 0.8s linear infinite;
171
+ }
172
+ .ursa-update-gray .ursa-spinner {
173
+ border-color: rgba(128, 128, 128, 0.3);
174
+ border-top-color: rgba(128, 128, 128, 0.8);
175
+ }
176
+ .ursa-update-green .ursa-spinner {
177
+ border-color: rgba(76, 175, 80, 0.3);
178
+ border-top-color: rgba(76, 175, 80, 0.9);
179
+ }
180
+ @keyframes ursa-spin {
181
+ to { transform: rotate(360deg); }
182
+ }
183
+
148
184
  .widget-button {
149
185
  display: flex;
150
186
  align-items: center;
@@ -174,7 +210,7 @@ nav#nav-global .nav-right-controls {
174
210
  pointer-events: none;
175
211
  }
176
212
 
177
- /* Widget dropdown panel */
213
+ /* Widget dropdown panel (right-side, default) */
178
214
  .widget-dropdown {
179
215
  position: fixed;
180
216
  top: var(--global-nav-height);
@@ -190,10 +226,54 @@ nav#nav-global .nav-right-controls {
190
226
  transition: opacity 0.15s ease;
191
227
  }
192
228
 
229
+ /* Widget dropdown panel (left-side) */
230
+ .widget-dropdown.widget-dropdown-left {
231
+ right: auto;
232
+ left: 0;
233
+ border-left: none;
234
+ border-right: 1px solid var(--widget-border);
235
+ box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.2);
236
+ }
237
+
193
238
  .widget-dropdown.hidden {
194
239
  display: none;
195
240
  }
196
241
 
242
+ /* Widget header with title and close button */
243
+ .widget-header {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: space-between;
247
+ padding: 0.5rem 0.75rem;
248
+ border-bottom: 1px solid var(--widget-border);
249
+ }
250
+
251
+ .widget-header-title {
252
+ font-weight: 600;
253
+ font-size: 0.85rem;
254
+ text-transform: uppercase;
255
+ letter-spacing: 0.03em;
256
+ opacity: 0.7;
257
+ }
258
+
259
+ .widget-close-btn {
260
+ background: none;
261
+ border: none;
262
+ color: var(--text-color);
263
+ font-size: 1rem;
264
+ cursor: pointer;
265
+ opacity: 0.5;
266
+ padding: 4px 8px;
267
+ line-height: 1;
268
+ border-radius: 3px;
269
+ transition: opacity 0.15s ease, background-color 0.15s ease;
270
+ }
271
+
272
+ .widget-close-btn:hover {
273
+ opacity: 1;
274
+ background-color: rgba(128, 128, 128, 0.2);
275
+ }
276
+
197
277
  /* Widget content panels — only the active one is visible */
198
278
  .widget-content {
199
279
  display: none;
@@ -374,6 +454,68 @@ nav#nav-global .nav-right-controls {
374
454
  font-style: italic;
375
455
  }
376
456
 
457
+ /* --- Recent Activity Widget --- */
458
+ .recent-activity-list {
459
+ padding: 0;
460
+ }
461
+
462
+ .recent-activity-loading,
463
+ .recent-activity-empty {
464
+ padding: 1.5rem 1rem;
465
+ text-align: center;
466
+ color: var(--text-color);
467
+ opacity: 0.6;
468
+ font-style: italic;
469
+ font-size: 0.85rem;
470
+ }
471
+
472
+ .recent-activity-items {
473
+ list-style: none;
474
+ margin: 0;
475
+ padding: 0;
476
+ }
477
+
478
+ .recent-activity-item {
479
+ display: flex;
480
+ align-items: baseline;
481
+ justify-content: space-between;
482
+ gap: 0.75rem;
483
+ padding: 0.5rem 0.75rem;
484
+ border-bottom: 1px solid var(--widget-border);
485
+ transition: background-color 0.15s ease;
486
+ }
487
+
488
+ .recent-activity-item:last-child {
489
+ border-bottom: none;
490
+ }
491
+
492
+ .recent-activity-item:hover {
493
+ background-color: rgba(128, 128, 128, 0.1);
494
+ }
495
+
496
+ .recent-activity-link {
497
+ color: var(--text-color);
498
+ text-decoration: none;
499
+ font-size: 0.9rem;
500
+ flex: 1;
501
+ min-width: 0;
502
+ overflow: hidden;
503
+ text-overflow: ellipsis;
504
+ white-space: nowrap;
505
+ }
506
+
507
+ .recent-activity-link:hover {
508
+ text-decoration: underline;
509
+ }
510
+
511
+ .recent-activity-time {
512
+ font-size: 0.75rem;
513
+ color: var(--text-color);
514
+ opacity: 0.5;
515
+ white-space: nowrap;
516
+ flex-shrink: 0;
517
+ }
518
+
377
519
  /* Search functionality styles */
378
520
  .search-results {
379
521
  position: fixed;
@@ -558,6 +700,8 @@ nav#nav-main-top .top-menu-dropdown {
558
700
  top: 100%;
559
701
  left: 0;
560
702
  min-width: 200px;
703
+ max-height: calc(100vh - var(--global-nav-height) - 20px);
704
+ overflow-y: auto;
561
705
  background-color: var(--widget-bg);
562
706
  border: 1px solid var(--widget-border);
563
707
  border-radius: 4px;
@@ -612,6 +756,8 @@ nav#nav-main-top .top-menu-flyout {
612
756
  left: 100%;
613
757
  top: -1px;
614
758
  min-width: 200px;
759
+ max-height: calc(100vh - var(--global-nav-height) - 20px);
760
+ overflow-y: auto;
615
761
  background-color: var(--widget-bg);
616
762
  border: 1px solid var(--widget-border);
617
763
  border-radius: 4px;