kireji 0.2.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kireji",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A web framework for stateful, entropy-perfect, multi-origin web applications. Currently in alpha. Expect breaking changes for version 0. Use with caution!",
5
5
  "files": [
6
6
  "src/",
@@ -3,10 +3,7 @@ const tabBitDepths = [0n, 0n]
3
3
  const tabOffsets = [0n]
4
4
  const permutationSizes = [1n]
5
5
  const payloadSizes = [1n]
6
- const partOffsets = [0, allParts.length]
7
- const subjectCount = BigInt(allParts.reduce((subjectCount, part, i) =>
8
- partOffsets[i + 2] = subjectCount + part.filenames.length, allParts.length
9
- ))
6
+ const subjectCount = BigInt(allSubjects.length)
10
7
  const maxTabCount = subjectCount
11
8
  const LSB = []
12
9
  const powerFloor = 2n ** BigInt(subjectCount.toString(2).length - 1)
@@ -63,8 +60,6 @@ tabGroup.define({
63
60
  tabBitDepths: { value: tabBitDepths },
64
61
  permutationSizes: { value: permutationSizes },
65
62
  payloadSizes: { value: payloadSizes },
66
- partOffsets: { value: partOffsets },
67
- subjectCount: { value: subjectCount },
68
63
  maxTabCount: { value: maxTabCount },
69
64
  previousPart: { value: null, writable: true },
70
65
  tree: { value: null, writable: true },
@@ -1,4 +1,4 @@
1
- const indexOfLastPossibleTabSubject = tabGroup.subjectCount - 1n
1
+ const indexOfLastPossibleTabSubject = BigInt(allSubjects.length) - 1n
2
2
  const indexOfLastOpenTab = NUMBER_OF_TABS_OPEN - 1n
3
3
 
4
4
  let permutationFactorOfCurrentTabIndex = 1n
@@ -46,16 +46,7 @@
46
46
  display: none;
47
47
  }
48
48
 
49
- #kireji_app tab-[data-drag-preview] {
50
- position: fixed;
51
- pointer-events: none;
52
- }
53
-
54
- #kireji_app tab-[data-drag-preview] .close-tab {
55
- display: none;
56
- }
57
-
58
- #kireji_app tab- {
49
+ tab- {
59
50
  height: fit-content;
60
51
  white-space: pre;
61
52
  text-overflow: ellipsis;
@@ -71,12 +62,12 @@
71
62
  line-height: var(--tab-line-height);
72
63
  }
73
64
 
74
- #kireji_app tab- img {
65
+ tab- img {
75
66
  height: var(--tab-icon-size);
76
67
  margin: calc((var(--tab-group-height) - var(--tab-icon-size)) / 2) 0;
77
68
  }
78
69
 
79
- #kireji_app tab- .tab-button {
70
+ tab- .tab-button {
80
71
  font-size: var(--default-font-size);
81
72
  font-weight: 400;
82
73
  height: unset;
@@ -105,6 +96,20 @@
105
96
  z-index: 999;
106
97
  }
107
98
 
99
+ tab-[data-drag-preview] {
100
+ width: min-content;
101
+ pointer-events: none;
102
+ position: fixed;
103
+ }
104
+
105
+ tab-[data-drag-preview] .tab-button {
106
+ padding-right: calc(var(--spacing) / 1.5);
107
+ }
108
+
109
+ tab-[data-drag-preview] .close-tab {
110
+ display: none;
111
+ }
112
+
108
113
  #kireji_app tab-[data-drop-target="after"]::before {
109
114
  right: -1px;
110
115
  }
@@ -156,7 +161,7 @@ body.modern.light #kireji_app tab-:is([data-active], :not([data-active]):hover)
156
161
  background-color: var(--bg-light-est);
157
162
  }
158
163
 
159
- body.modern #kireji_app tab- {
164
+ body.modern tab- {
160
165
  box-shadow:
161
166
  inset 0 -2px 0 -1px var(--bg-un-mode),
162
167
  inset 0 2px 0 -1px var(--bg-un-mode),
@@ -169,7 +174,12 @@ body.modern #kireji_app tab-[data-active] {
169
174
  inset -2px 0 0 -1px var(--bg-un-mode);
170
175
  }
171
176
 
172
- body.modern #kireji_app tab- .tab-button {
177
+ body.modern>tab-[data-drag-preview] {
178
+ box-shadow: inset 0 0 0 1px var(--bg-un-mode);
179
+ background-color: var(--bg-light-est);
180
+ }
181
+
182
+ body.modern tab- .tab-button {
173
183
  line-height: var(--tab-group-height);
174
184
  }
175
185
 
@@ -179,10 +189,6 @@ body.modern #kireji_app tab- .tab-path {
179
189
  display: inline-block;
180
190
  }
181
191
 
182
- body.modern #kireji_app tab-[data-drag-preview] .tab-path {
183
- display: none;
184
- }
185
-
186
192
  body.vintage #kireji_app #tab-group {
187
193
  height: calc(4px + var(--tab-group-height));
188
194
  background: transparent;
@@ -222,11 +228,11 @@ body.vintage #kireji_app tab-[data-active] {
222
228
  z-index: 2;
223
229
  }
224
230
 
225
- body.vintage #kireji_app tab- .tab-path {
231
+ /*body.vintage #kireji_app tab- .tab-path {
226
232
  display: none;
227
- }
233
+ }*/
228
234
 
229
- body.vintage #kireji_app tab-:not([data-active]) {
235
+ body.vintage tab-:not([data-active]) {
230
236
  height: var(--tab-group-height);
231
237
  position: relative;
232
238
  top: 2px;
@@ -239,7 +245,7 @@ body.vintage #kireji_app tab-[data-active] .tab-button {
239
245
  padding-left: calc(var(--spacing) / 1.5 + 2px);
240
246
  }
241
247
 
242
- body.vintage #kireji_app tab- .tab-button {
248
+ body.vintage tab- .tab-button {
243
249
  padding-right: calc(var(--tab-group-height) + 8px);
244
250
  border-top-left-radius: 2px;
245
251
  border-top-right-radius: 2px;
@@ -251,7 +257,7 @@ body.vintage #kireji_app tab- .tab-button {
251
257
  inset -3px 0 0 -1px var(--bg-dark);
252
258
  }
253
259
 
254
- body.dark.vintage #kireji_app tab- .tab-button {
260
+ body.dark.vintage tab- .tab-button {
255
261
  box-shadow:
256
262
  inset -2px 0 0 -1px black,
257
263
  inset 1px 1px 0 0 var(--bg-light-er),
@@ -279,7 +285,7 @@ body.vintage #kireji_app tab-[data-active] .tab-button:focus::after {
279
285
  --inset: 4px;
280
286
  }
281
287
 
282
- body.vintage #kireji_app tab-[data-drag-preview] .tab-button {
288
+ body.vintage>tab-[data-drag-preview] .tab-button {
283
289
  box-shadow: inset 0 0 0 1px black;
284
290
  }
285
291
 
@@ -37,7 +37,7 @@ if (numberOfTabsOpen !== tabGroup.openTabs.length || tabGroup.permutationRouteID
37
37
  tabGroup.tree = new tabGroup.FenwickTree()
38
38
 
39
39
  const indexOfLastOpenTab = numberOfTabsOpen - 1n
40
- const indexOfLastPossibleTabSubject = tabGroup.subjectCount - 1n
40
+ const indexOfLastPossibleTabSubject = BigInt(allSubjects.length) - 1n
41
41
 
42
42
  for (let currentTabIndex = 0n; currentTabIndex < numberOfTabsOpen; currentTabIndex++) {
43
43
 
@@ -56,29 +56,12 @@ if (numberOfTabsOpen !== tabGroup.openTabs.length || tabGroup.permutationRouteID
56
56
  const payload = payloadRouteID % tabGroup.payloadCardinality
57
57
  payloadRouteID /= tabGroup.payloadCardinality
58
58
 
59
- // Using embedded match logic, split apart the true index into usable file data.
60
- if (trueIndexOfActiveTabSubject < allParts.length) {
61
-
62
- // The index is in the first plane; it maps to the set of parts themselves.
63
- tabGroup.openTabs[currentTabIndex] = {
64
- part: allParts[trueIndexOfActiveTabSubject],
65
- payload
66
- }
67
- } else {
68
-
69
- // The index is among the later planes, indicating a specific file defined on a specific part.
70
- for (let indexOfPlane = 1; indexOfPlane <= allParts.length; indexOfPlane++) {
71
- const nextPlaneIndex = indexOfPlane + 1
72
- if (nextPlaneIndex > allParts.length || trueIndexOfActiveTabSubject < tabGroup.partOffsets[nextPlaneIndex]) {
73
- const indexOfOwningPart = indexOfPlane - 1
74
- const part = allParts[indexOfOwningPart]
75
- const firstIndexOfCurrentPlane = tabGroup.partOffsets[indexOfPlane]
76
- const indexOfSubjectWithinCurrentPlane = Number(trueIndexOfActiveTabSubject) - firstIndexOfCurrentPlane
77
- const filename = part.filenames[indexOfSubjectWithinCurrentPlane]
78
- tabGroup.openTabs[currentTabIndex] = { part, filename, payload }
79
- break
80
- }
81
- }
59
+ // Match the subject index with the actual subject.
60
+ const [host, filename] = allSubjects[Number(trueIndexOfActiveTabSubject)]
61
+ tabGroup.openTabs[currentTabIndex] = {
62
+ part: partsByHost[host],
63
+ filename,
64
+ payload
82
65
  }
83
66
  }
84
67
 
@@ -7,10 +7,9 @@ tabGroup.tree = new tabGroup.FenwickTree()
7
7
  let permutationRouteID = 0n
8
8
  for (let currentTabIndex = 0n; currentTabIndex < numberOfTabsOpen; currentTabIndex++) {
9
9
 
10
- // Use an embedded match to combine the current tab's file data into an absolute tab subject index.
10
+ // Get the absolute tab subject index.
11
11
  const { part, filename } = TABS[Number(currentTabIndex)]
12
- const index = allParts.indexOf(part)
13
- const trueIndexOfActiveTabSubject = BigInt(filename ? tabGroup.partOffsets[index + 1] + part.filenames.indexOf(filename) : index)
12
+ const trueIndexOfActiveTabSubject = BigInt(subjectIndices.get(part.host + (filename ? "/" + filename : "")))
14
13
 
15
14
  // Use the Fenwick tree to obtain the availability-based index of the tab subject in the list of remaining subjects.
16
15
  const availabilityIndexOfActiveTabSubject = tabGroup.tree.query(trueIndexOfActiveTabSubject - 1n)
@@ -57,10 +57,6 @@ declare interface IKirejiAppTabGroup
57
57
  readonly activeTabIndex: number
58
58
  /** The index of the preview tab, if one exists. The preview tab is the tab which can be replaced when opening a new tab. */
59
59
  readonly previewTabIndex?: number
60
- /** The array of memoized offset route IDs corresponding to the start of each part's filename plane. */
61
- readonly partOffsets: number[]
62
- /** The total number of tab subjects available - the number of _different_ filenames and part summaries that can be the focus of their own tab (regardless of the maximum number of tabs which can be open at once). */
63
- readonly subjectCount: bigint
64
60
  /** A data type which can be used to performantly rank and unrank permutation indices. */
65
61
  readonly FenwickTree: typeof FenwickTree
66
62
  }
@@ -9,14 +9,14 @@
9
9
  --sidebar-width: calc(var(--tool-bar-width) + var(--sidebar-view-width));
10
10
  }
11
11
 
12
- body.modern #kireji_app {
12
+ body.modern {
13
13
  --tab-group-height: calc(var(--tab-line-height) + var(--spacing) / 1.5);
14
14
  --tab-line-height: 26px;
15
15
  --crumbs-height: 26px;
16
16
  --tab-icon-size: 20px;
17
17
  }
18
18
 
19
- body.vintage #kireji_app {
19
+ body.vintage {
20
20
  --tab-group-height: calc(var(--tab-line-height) + 2px);
21
21
  --tab-line-height: 20px;
22
22
  --crumbs-height: 20px;
package/src/build.js CHANGED
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
- function ƒ(_) {
2
+ function ƒ(_, compressedSubjectOrigins) {
3
3
 
4
4
  // Initialization
5
5
  const
6
6
  environment = globalThis.constructor === globalThis.Window ? "client" : globalThis.constructor === globalThis.ServiceWorkerGlobalScope ? "worker" : (
7
7
  Object.defineProperty(_, "$", { value: (f => x => f(x).toString().trim())(require("child_process").execSync) }),
8
8
  _.command = process.argv[2] || "help",
9
- _.local = _.command === "dev",
9
+ _.local = _.command === "dev" ? "1" : "0",
10
10
  require.main === module && (
11
11
  _.branch = _.$("git rev-parse --abbrev-ref HEAD").toString().trim(),
12
12
  _.gitSHA = _.$("git rev-parse HEAD").toString().trim(),
13
- _.version = (([M, m, p], c) => _.local ? +M && c === "major" ? `${++M}.0.0` : c === "minor" || (!+M && c === "major") ? `${M}.${++m}.0` : `${M}.${m}.${++p}` : `${M}.${m}.${p}`)(_.$("git log -1 --pretty=%s").toString().match(/^\s*(\d+\.\d+\.\d+)/)[1].split("."), _.change),
13
+ _.version = (([M, m, p], c) => +_.local ? +M && c === "major" ? `${++M}.0.0` : c === "minor" || (!+M && c === "major") ? `${M}.${++m}.0` : `${M}.${m}.${++p}` : `${M}.${m}.${p}`)(_.$("git log -1 --pretty=%s").toString().match(/^\s*(\d+\.\d+\.\d+)/)[1].split("."), _.change),
14
14
  _.modified = _.$('git show -s --format=%ci HEAD').toString().trim(),
15
- _.ETag = `"${_.version}.${_.gitSHA.slice(0, 7)}${_.local ? ("." + Math.random()).slice(2, 10) : ""}"`,
15
+ _.ETag = `"${_.version}.${_.gitSHA.slice(0, 7)}${+_.local ? ("." + Math.random()).slice(2, 10) : ""}"`,
16
16
  _.name = __dirname.split(/[\\/]/).filter(Boolean).at(-4)
17
17
  ),
18
18
  "node"
19
19
  ),
20
- production = _.branch === "main" && environment !== "node" && !_.local,
20
+ production = _.branch === "main" && environment !== "node" && !(+_.local),
21
21
  welcomeMessage = `
22
22
  ▌ ▘ ▘▘ ${_.name}
23
23
  k = ▙▘▌▛▘█▌ ▌▌ ${_.branch}
@@ -132,6 +132,9 @@ function ƒ(_) {
132
132
  pathRadix = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_0",
133
133
  encodeSegment = routeID => {
134
134
 
135
+ if (typeof routeID !== "bigint")
136
+ throw new RangeError(`Segment encoder can only encode a bigint type (got ${typeof routeID})`)
137
+
135
138
  if (routeID < 0n)
136
139
  throw new RangeError("Segment encoder cannot encode a negative route ID.")
137
140
 
@@ -230,16 +233,41 @@ function ƒ(_) {
230
233
  ┊ Now booting the Kireji Web Framework ┊
231
234
  ╰┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈╯
232
235
  `)
233
- const buildStartTime = Date.now()
236
+
237
+ const
238
+ bootStartTime = Date.now(),
239
+ allSubjects = [],
240
+ subjectIndices = new Map(),
241
+ subjectOrigins = new Map()
242
+
234
243
  if (environment === "node" && require.main === module) {
244
+ subjectOrigins.set("", true)
245
+
235
246
  const
236
- stats = { fileCount: 0, domainCount: 0 },
247
+ stats = { fileCount: 0, partCount: 0, ignoreCount: 0 },
237
248
  { readdirSync: readFolder, readFileSync: readFile, existsSync: exists } = require("fs"),
238
- { resolve } = require("path"),
239
- configPath = resolve(__dirname, "../../../src/kireji.json")
240
-
241
- if (exists(configPath))
242
- Object.assign(_, require(configPath))
249
+ { resolve } = require("path")
250
+
251
+ logScope(1, "Merging Configurations", configLog => {
252
+ for (const key in _) {
253
+ const value = _[key]
254
+ if (typeof value !== "string")
255
+ throw new TypeError(`Internal Error: found framework default config parameter that was not a string (typeof "${key}" === ${typeof value}).`)
256
+ subjectOrigins.set("/" + key, false)
257
+ }
258
+ const projectConfigPath = resolve(__dirname, "../../../src/kireji.json")
259
+ if (exists(projectConfigPath)) {
260
+ const projectConfig = require(projectConfigPath)
261
+ for (const key in projectConfig) {
262
+ const value = projectConfig[key]
263
+ if (typeof value !== "string")
264
+ throw new TypeError(`All config values in kireji.json must be strings (found typeof "${key}" === ${typeof value}).`)
265
+ _[key] = value
266
+ subjectOrigins.set("/" + key, true)
267
+ }
268
+ }
269
+ configLog("Done.")
270
+ })
243
271
 
244
272
  logScope(1, "Archiving Repository", archiveLog => {
245
273
  let lastLogFlush = Date.now()
@@ -256,88 +284,69 @@ function ƒ(_) {
256
284
  lastLogFlush = now
257
285
  }
258
286
  },
259
- readRecursive = (host, baseDir, overlayDir, part, depth) => {
260
- if (host && host.length > 253)
261
- throw SyntaxError(`requested host is ${host.length} characters long, exceeding the maximum domain name length of 253. \n${host}`)
287
+ frameworkRoot = __dirname,
288
+ projectRoot = resolve(__dirname, "../../../src"),
289
+ readRecursive = domains => {
262
290
 
263
- // 1. Get entries from BOTH directories if they exist
264
- const baseEntries = exists(baseDir) ? readFolder(baseDir, { withFileTypes: true }) : []
265
- const overlayEntries = exists(overlayDir) ? readFolder(overlayDir, { withFileTypes: true }) : []
291
+ const host = [...domains].reverse().join(".")
266
292
 
267
- // 2. Create a unified map of names to entry types (Overlay wins)
268
- const unifiedEntries = new Map()
293
+ if (host.length > 253)
294
+ throw SyntaxError(`Part host would be ${host.length} characters long, exceeding the maximum host name length of 253 (${host}).`)
269
295
 
270
- // Base entries first
271
- for (const entry of baseEntries)
272
- unifiedEntries.set(entry.name, { entry, source: 'base' })
296
+ const mergedSubjects = new Map()
273
297
 
274
- // Overlays overwrite base definitions
275
- for (const entry of overlayEntries)
276
- unifiedEntries.set(entry.name, { entry, source: 'overlay' })
298
+ const frameworkPath = resolve(frameworkRoot, ...domains)
299
+ if (exists(frameworkPath))
300
+ for (const subject of readFolder(frameworkPath, { withFileTypes: true }))
301
+ mergedSubjects.set(subject.name, { subject, path: frameworkPath })
277
302
 
278
- const filenames = []
303
+ const projectPath = resolve(projectRoot, ...domains)
304
+ if (exists(projectPath))
305
+ for (const subject of readFolder(projectPath, { withFileTypes: true }))
306
+ mergedSubjects.set(subject.name, { subject, path: projectPath })
279
307
 
280
- for (const [itemName, { entry, source }] of unifiedEntries) {
281
- // Ignore logic (TS files, hidden files, etc)
282
- if (itemName.startsWith(".") || itemName === "Icon" || (!host && itemName === "build.js") || itemName.endsWith(".ts")) {
283
- bufferLog(4, "".padEnd(depth, " ") + `⬚ ${itemName.padEnd(20, " ")} - ignored`)
284
- continue
285
- }
308
+ const part = domains.length ? {} : _
286
309
 
287
- // Determine paths for the next step
288
- const nextBase = `${baseDir}/${itemName}`
289
- const nextOverlay = `${overlayDir}/${itemName}`
290
-
291
- // Logic for host string construction remains...
292
- const filePath = (host ? host.split(".").reverse().join("/") + "/" : "") + itemName
293
-
294
- if (entry.isDirectory()) {
295
- bufferLog(2, "".padEnd(depth, " ") + `▼ ${itemName}/`)
296
-
297
- // RECURSION: Pass both potential paths down
298
- readRecursive(
299
- host ? (itemName ? itemName + "." + host : host) : itemName ?? "",
300
- nextBase,
301
- nextOverlay,
302
- (part[itemName] = part[itemName] || {}), // Ensure we merge into existing object if base/overlay share folder
303
- depth + 1
304
- )
305
-
306
- } else if (entry.isFile()) {
307
- // Prioritize overlay file path, fallback to base
308
- const actualPath = (source === 'overlay') ? nextOverlay : nextBase
309
- filenames.push([itemName, actualPath])
310
+ for (const [itemName, { subject, path }] of mergedSubjects)
311
+ if (itemName.startsWith(".") || itemName === "Icon" || (!host && itemName === "build.js") || itemName.endsWith(".ts")) {
312
+ stats.ignoreCount++
313
+ bufferLog(4, "".padEnd(domains.length, " ") + `⬚ ${itemName.padEnd(20, " ")} - ignored`)
314
+ } else if (subject.isDirectory()) {
315
+ stats.partCount++
316
+ bufferLog(2, "".padEnd(domains.length, " ") + `▼ ${itemName}/`)
317
+ subjectOrigins.set(itemName + (host ? "." + host : ""), path === projectPath)
318
+ part[itemName] = readRecursive([...domains, itemName])
319
+ } else if (subject.isFile()) {
320
+ stats.fileCount++
321
+ const isBinary = itemName.endsWith(".png") || itemName.endsWith(".gif")
322
+ bufferLog(3, "".padEnd(domains.length, " ") + `${isBinary ? "▣" : ""} ${itemName}`)
323
+ subjectOrigins.set(host + "/" + itemName, path === projectPath)
324
+ part[itemName] = readFile(resolve(path, itemName), isBinary ? "base64" : "utf-8")
325
+ } else {
326
+ stats.ignoreCount++
327
+ bufferLog(4, "".padEnd(domains.length, " ") + `⬚ ${itemName.padEnd(20, " ")} - ignored (exotic type)`)
310
328
  }
311
- }
312
-
313
- // File processing (remains largely the same, but uses the prioritized path)
314
- for (const [filename, filePath] of filenames) {
315
- const isBinary = filename.endsWith("png") || filename.endsWith("gif")
316
- const content = readFile(filePath, isBinary ? "base64" : "utf-8")
317
-
318
- part[filename] = content
319
- stats.fileCount++
320
- bufferLog(3, "".padEnd(depth, " ") + `${isBinary ? "▣" : "≡"} ${filename}`)
321
- }
322
329
 
323
- stats.domainCount++
330
+ return part
324
331
  }
325
332
 
326
- readRecursive("", __dirname, resolve(__dirname, "../../../src"), _, 0)
333
+ readRecursive([])
327
334
 
328
335
  if (batchedLogs.length)
329
336
  archiveLog(batchedLogs.join("\n") + "\n")
330
337
 
331
- archiveLog(`Archived in ${Date.now() - buildStartTime}ms`)
338
+ archiveLog(`Archived in ${Date.now() - bootStartTime}ms`)
332
339
  })
333
340
 
334
- logScope(2, "\nLogging Domain Stats", () => {
341
+ logScope(2, "\nDomain Stats", () => {
335
342
  logAny(2, [{
336
- Parts: { Amount: stats.domainCount },
343
+ Parts: { Amount: stats.partCount },
337
344
  Files: { Amount: stats.fileCount },
345
+ Ignored: { Amount: stats.ignoreCount }
338
346
  }], "table")
339
347
  })
340
348
  }
349
+
341
350
  logScope(1, "\nRecursively Hydrating Parts", hydrateLog => {
342
351
 
343
352
  class SourceMappedFile {
@@ -404,7 +413,7 @@ function ƒ(_) {
404
413
  packAndMap(url) {
405
414
  const sourceFile = this
406
415
  const script = sourceFile.lines.join("\n")
407
- return _.mapping
416
+ return +_.mapping
408
417
  ? script +
409
418
  `
410
419
  //${"#"} sourceMappingURL=data:application/json;charset=utf-8;base64,${btoaUnicode(sourceFile.getMap())}${url
@@ -474,7 +483,7 @@ function ƒ(_) {
474
483
  earlyImageSources = [],
475
484
  partsByHost = {},
476
485
  preHydrationArchive = serialize(_),
477
- hydrateRecursive = (part, domains = []) => {
486
+ hydratePartsRecursive = (part, domains = []) => {
478
487
 
479
488
  let host
480
489
 
@@ -516,7 +525,9 @@ function ƒ(_) {
516
525
  const extendsString = part.manifest.extends ?? "part"
517
526
  const relativeStepsBack = extendsString.match(/\.*$/)[0].length
518
527
  const typename = relativeStepsBack ? `${extendsString.slice(0, -relativeStepsBack)}.${domains.slice(relativeStepsBack - 1).join(".")}` : extendsString.includes(".") ? extendsString : `${extendsString}.abstract.parts`
519
- prototype = hydrateRecursive(typename)
528
+ prototype = hydratePartsRecursive(typename)
529
+ if (!prototype.isAbstract)
530
+ throw new Error(`Hydration Error: parts can only extend abstract parts (${host} tried to extend ${prototype.host}).`)
520
531
  Object.setPrototypeOf(part, prototype)
521
532
  part.define({
522
533
  isAbstract: { value: part.manifest.abstract }
@@ -662,6 +673,8 @@ function ƒ(_) {
662
673
  earlyImageSources.push([part, fn.slice(6)])
663
674
  imageSources.push([part, fn])
664
675
  }
676
+ subjectIndices.set(`${host}/${fn}`, allSubjects.length)
677
+ allSubjects.push([host, fn])
665
678
  }
666
679
  for (const methodID in part.manifest)
667
680
  if (!["extends", "abstract"].includes(methodID))
@@ -690,7 +703,7 @@ function ƒ(_) {
690
703
  part.define({ [identifier]: { value: childPart } })
691
704
  }
692
705
 
693
- hydrateRecursive(childPart, [subdomain, ...domains])
706
+ hydratePartsRecursive(childPart, [subdomain, ...domains])
694
707
 
695
708
  childPart.define({ "..": { value: part } })
696
709
 
@@ -699,10 +712,23 @@ function ƒ(_) {
699
712
  }
700
713
  if (!part.isAbstract) instances.push(part)
701
714
  allParts.push(part)
715
+ subjectIndices.set(host, allSubjects.length)
716
+ allSubjects.push([host])
702
717
  })
703
718
 
704
719
  return part
705
720
  },
721
+ hydrateSubjectOrigins = () => {
722
+ if (environment === "node") {
723
+ const bits = allSubjects.map(([host, fn]) => +subjectOrigins.get(host + (fn ? "/" + fn : "")))
724
+ const bitString = bits.join("")
725
+ compressedSubjectOrigins = encodeSegment(BigInt("0b" + bitString))
726
+ } else {
727
+ const bitString = decodeSegment(compressedSubjectOrigins).toString(2).padStart(allSubjects.length, "0")
728
+ const bits = [...bitString]
729
+ allSubjects.forEach(([host, fn], index) => subjectOrigins.set(host + (fn ? "/" + fn : ""), bits[index] === "1"))
730
+ }
731
+ },
706
732
  countAndSortInheritorsRecursive = part => {
707
733
  if (part.totalInheritors !== undefined)
708
734
  return part.totalInheritors
@@ -717,7 +743,8 @@ function ƒ(_) {
717
743
  return totalInheritors
718
744
  }
719
745
 
720
- hydrateRecursive(_)
746
+ hydratePartsRecursive(_)
747
+ hydrateSubjectOrigins()
721
748
  countAndSortInheritorsRecursive(_.parts.abstract.part)
722
749
  hydrateLog(`\nParts hydrated in ${Date.now() - hydrationStartTime}ms.`)
723
750
 
@@ -752,10 +779,10 @@ function ƒ(_) {
752
779
  })
753
780
 
754
781
  logScope(1, "\nBuilding Part Instances", buildLog => {
755
- const buildStartTime = Date.now()
782
+ const bootStartTime = Date.now()
756
783
  for (const part of instances)
757
784
  part.startBuild()
758
- buildLog(`Parts built in ${Date.now() - buildStartTime}ms.`)
785
+ buildLog(`Parts built in ${Date.now() - bootStartTime}ms.`)
759
786
  })
760
787
 
761
788
  logScope(2, "\nLogging Part Entropy", () => {
@@ -795,7 +822,7 @@ function ƒ(_) {
795
822
 
796
823
  bootLog(`
797
824
  ╭┈┈┈┈┈┈┈┈┈┈┈ BOOT SUCCEEDED ┈┈┈┈┈┈┈┈┈┈┈╮
798
- ┊ Booted in ${(Date.now() - buildStartTime + "ms.").padEnd(27, " ")}┊
825
+ ┊ Booted in ${(Date.now() - bootStartTime + "ms.").padEnd(27, " ")}┊
799
826
  ┊ ┊
800
827
  ┊ End of synchronous script execution. ┊
801
828
  ╰┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈╯
@@ -804,12 +831,12 @@ function ƒ(_) {
804
831
  }
805
832
 
806
833
  ƒ({
807
- verbosity: 1,
834
+ verbosity: "1",
808
835
  // TODO: Fix source mapping bugs.
809
- mapping: false,
836
+ mapping: "0",
810
837
  change: "major",
811
- hangHydration: 0,
812
- haltHydration: false,
838
+ hangHydration: "0",
839
+ haltHydration: "0",
813
840
  defaultApplicationHost: "kireji.app",
814
- port: 3000
841
+ port: "3000"
815
842
  })
package/src/build.js_.js CHANGED
@@ -1,13 +1,6 @@
1
1
  const boilerplate = ƒ.toString()
2
-
3
2
  const sourceFile = new SourceMappedFile("../", undefined, "build.js")
4
3
  sourceFile.addSection(boilerplate, sourceFile.addSource("build.js", boilerplate))
5
- sourceFile.addSection(`\nƒ(${JSON.stringify(_, (k, v) => {
6
- if (typeof v === "bigint")
7
- return v.toString() + "n"
8
-
9
- return v
10
-
11
- }, 1)})`, sourceFile.addSource(property.filename, property.content))
4
+ sourceFile.addSection(`\nƒ(${serialize(_)}, ${serialize(compressedSubjectOrigins)})`, sourceFile.addSource(property.filename, property.content))
12
5
  const script = sourceFile.packAndMap()
13
6
  return script
@@ -1,5 +1,16 @@
1
+ if (!scroller.query)
2
+ throw new ReferenceError(`Scrollers must define a css-style "query" property which can select the parent element of the scroller (from ${scroller.host}).`)
3
+
1
4
  scroller.container = Q(scroller.query + ">scroller-")
5
+
6
+ if (!scroller.container)
7
+ throw new ReferenceError(`Could not find scroller parent element via query "${scroller.query}" (from ${scroller.host}).`)
8
+
2
9
  scroller.scrollBar = Q(scroller.query + ">scroll-bar")
10
+
11
+ if (!scroller.scrollBar)
12
+ throw new ReferenceError(`Could not find scroller components in the element "${scroller.query}" (from ${scroller.host}). Use scroller.wrap to wrap the HTML contents of the element with the scroller's container and include the custom scroll bar HTML (from ${scroller.host}).`)
13
+
3
14
  scroller.thumb = scroller.scrollBar.querySelector("thumb-")
4
15
  scroller.content = Q(scroller.query + ">scroller->scroll-content")
5
16
  scroller.container.scrollTop = scroller.fraction * scroller.container.scrollHeight
@@ -4,7 +4,7 @@ await Promise.all([
4
4
  ])
5
5
 
6
6
  logScope(0, "Finalizing Hydration", log => {
7
- if (_.haltHydration && !production)
7
+ if (+_.haltHydration && !production)
8
8
  warn('Intentionally blocked hydration.')
9
9
  else {
10
10
 
@@ -15,6 +15,8 @@ for (const code of hotKeys.pressed)
15
15
  terminalKeys.add(code.slice(5).toLowerCase())
16
16
  else if (/^(Minus|Equal|Semicolon|Quote|Comma|Period|Slash|Backquote|Backslash|Bracket(Right|Left)|Arrow(Up|Down|Left|Right)|Escape|Tab|Enter|Backspace)$/.test(code))
17
17
  terminalKeys.add(code.toLowerCase())
18
+ else if (!production && code === "F8")
19
+ debugger
18
20
  else debug("Unhandled Key Code: " + code)
19
21
 
20
22
  const combo = [...terminalKeys].sort()
@@ -199,7 +199,7 @@ const httpServer = require('http').createServer((request, response) => logServer
199
199
  /** @type {IVersionedExports} */
200
200
  let destinationExports
201
201
  try {
202
- destinationExports = destinationVersion === _.version ? currentExports : require(`../.versions/${destinationVersion}.js`)
202
+ destinationExports = destinationVersion === _.version ? currentExports : require(`../../../.versions/${destinationVersion}.js`)
203
203
  } catch {
204
204
  throw `Bad Version: ${destinationVersion}`
205
205
  }
@@ -211,7 +211,7 @@ const httpServer = require('http').createServer((request, response) => logServer
211
211
  /** @type {IVersionedExports} */
212
212
  let sourceExports
213
213
  try {
214
- sourceExports = require(`../.versions/${sourceVersion}.js`)
214
+ sourceExports = require(`../../../.versions/${sourceVersion}.js`)
215
215
  } catch {
216
216
  throw `Bad Version: ${sourceVersion}`
217
217
  }
@@ -260,4 +260,4 @@ const httpServer = require('http').createServer((request, response) => logServer
260
260
  }
261
261
  ))
262
262
 
263
- httpServer.listen(_.port, () => logScope(0, `Server Ready - http://localhost:${_.port}`))
263
+ httpServer.listen(+_.port, () => logScope(0, `Server Ready - http://localhost:${_.port}`))
@@ -57,13 +57,13 @@ task-bar button,
57
57
  task-bar menu-button {
58
58
  height: var(--icon-size);
59
59
  line-height: var(--icon-size);
60
- cursor: pointer;
61
60
  font-size: calc(var(--icon-size) * 1.2);
62
61
  }
63
62
 
64
63
  button:hover,
65
64
  menu-button:hover {
66
65
  color: var(--accent);
66
+ cursor: pointer;
67
67
  }
68
68
 
69
69
  menu-button>img {
@@ -70,7 +70,7 @@ pointer.handle({
70
70
  desktopIcons.setRouteID(0n)
71
71
 
72
72
  const application = desktopIcons.superset[Number(TARGET_ELEMENT.getAttribute("data-index"))]
73
- const targetLocation = (_.local ? `http://${application.host}.localhost:${_.port}` : `https://${application.host}`) + encodePathname(_.routeID)
73
+ const targetLocation = (+_.local ? `http://${application.host}.localhost:${_.port}` : `https://${application.host}`) + encodePathname(_.routeID)
74
74
  location = targetLocation
75
75
  },
76
76
  getIntersectionMask() {
package/src/point.js CHANGED
@@ -10,7 +10,7 @@ pointer.handle({
10
10
  if (host !== _.application.host) {
11
11
  if (host in _.applications) {
12
12
  if (pathname === "/" && !hash) {
13
- const targetLocation = (_.local ? `http://${host}.localhost:${_.port}` : `https://${host}`) + encodePathname(_.routeID)
13
+ const targetLocation = (+_.local ? `http://${host}.localhost:${_.port}` : `https://${host}`) + encodePathname(_.routeID)
14
14
  location = targetLocation
15
15
  } else warn(`Cross-host navigation and canonical pathname are not yet handled because of ambiguity between simply changing applications and linking to a canonical home page (while attempting to navigate to "${TARGET_ELEMENT.href}").`)
16
16
  } else window.open(TARGET_ELEMENT.href, '_blank')
package/src/type.d.ts CHANGED
@@ -289,7 +289,17 @@ declare function randomBits(bigCount: number): bigint
289
289
  declare function randomRouteID(cardinality: bigint): bigint
290
290
  /** Returns a random boolean value. */
291
291
  declare function flipCoin(): boolean
292
- /** The immutable list of runtime instances for the root space, in order of when they were fully hydrated. */
292
+ /** The immutable list of runtime instances for the root space, in order of when the were reached during recursive part hydration. */
293
293
  declare const instances: IPartAny[]
294
- /** The immutable list of every part in the root space, in order of when they were fully hydrated. */
295
- declare const allParts: IPartAny[]
294
+ /** The immutable list of every part in the root space, in order of when the were reached during recursive part hydration. */
295
+ declare const allParts: IPartAny[]
296
+ /** The immutable list of every part host and filename combination, in order of when the were reached during recursive part hydration. */
297
+ declare const allSubjects: [host: string, filename?: string]
298
+ /** The Unix timestamp (in ms) when the current instance of the framework started booting. */
299
+ declare const bootStartTime: number
300
+ /** A map that provides information about whether a subject was defined in the project repository (maps to true) or only in the kireji framework package (maps to false). */
301
+ declare const subjectOrigins: Map<string, boolean>
302
+ /** A map that provides the index (in the `allSubjects` array) for the given subject. */
303
+ declare const subjectIndices: Map<string, boolean>
304
+ /** A base64-encoded string which represents a portable bitmask that compresses `subjectOrigins` in hydration order (because that order is identical between environments). */
305
+ declare const compressedSubjectOrigins: string