@selimssevgi/cube-design-angular 0.5.1 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@
4
4
 
5
5
  nothing yet.
6
6
 
7
+ ## 0.6.1 (4 november 2026: an empty square is seen whole on a short desktop window)
8
+
9
+ - **the square counts what stands above it.** `<cube-square>` held the square view to the window's height less a fixed 48 px, so under mosaic's top bar and head an empty piece's 5 × 5 (`least`) ran below a short desktop window. the room's `tall` is now the window's height less where the square starts on the page and 16 px below, never less than 320: the pure `squareTall(windowHeight, top, { below, least })`, `SQUARE_ROOM_BELOW` (16) and `SQUARE_LEAST_TALL` (320). it is measured again when the page above it changes (a head's line arriving). an app changes nothing; a square low on a page is smaller than before, and `least` follows it (3 × 3 or 4 × 4 where the window is short).
10
+
11
+ ## 0.6.0 (4 november 2026: the report kit, so mosaic, cubes and anlat let anyone tell us something)
12
+
13
+ - **telling us**, the report kit (`ReportKind`, `REPORT_KINDS`, `ReportBuild`, `ReportApp`, `REPORT_APP`, `ReportDiagnostics`, `ReportErrorHandler`, `reportInterceptor`, `provideReportDiagnostics`, `REPORT_INCIDENTS_KEPT`, `REPORT_INCIDENTS_MS`, `scrubbedPath`, `errorLine`, `ReportContext`, `reportContext`, `contextOf`, `routePattern`, `environmentOf`, `technicalLines`, `ReportDraft`, `findJustSaved`, `JUST_SAVED_MINUTES`, `SavedReportLike`, `ReportClient`, `ReportInput`, `ReportRequest`, `SavedReport`, `reportRequest`, `reportRefusal`): mosaic's feedback capture made headless (lang-app-fe `core/feedback`), so every cube app gathers the same and sends it to the backend's `POST /feedback`, the one door every signed-in account may use, into the owner's todarch (the study *feedback in every cube app*, step B). it gathers the app, the app's own words for the screen, the route pattern, the page's address without its query or fragment, the environment, the held piece's key, the build and device and, for a bug, the last 5 errors and failed requests of 10 minutes; never a heading or a name a person wrote. the draft is kept on the phone; sending again is never twice (the backend answers the same words from the same reporter within 15 minutes with the same report; `justSaved` looks first where the account may read the reports); a refusal says why in the backend's words (`reportRefusal`); and `undo`. a report may be not known yet (`kind: null`, no `type`, no kind placed). no words, no sheet.
14
+ - **tell**, the last part of every context cube (selim's decision of 4 november 2026): `ContextPartKey` has `tell`, `CONTEXT_PART_ORDER` ends with it and `orderParts` keeps it last, after the parts an app adds (`CONTEXT_LAST_PARTS`). `tellLevel(words)` (`TellData`) gives its four pieces, something broke, make it better, an idea and not known yet, in `ContextWords.tell` (`TellWords`); a tap ends the journey and the app opens its own sheet. `<cube-icon>` draws `tell`, `bug`, `improvement`, `idea` and `not-known`. an app that does not add tell sees no change.
15
+ - `@angular/router` is a peer dependency now (`reportContext` reads the route pattern); every app already has it.
16
+
7
17
  ## 0.5.1 (4 november 2026: an empty piece has room)
8
18
 
9
19
  - **`least` on `<cube-square>`** and the pure `squareLeast(side, { cell, min, max })` (`SQUARE_LEAST_CELL`, 200): a dashed square (something can be added) stands at least as many across as keep a place near 200 px, 3 × 3 on a phone, 4 × 4 in a desktop column, 5 × 5 at the widest; past that the tiles grow it as before. an empty piece is its own box first, then dashed places, never one place as wide as the screen (selim's pick, way c of `studies/empty-piece-places.md`, 4 november 2026: "this single add looks ridiculous"). `squareLayout` takes `least` too. on by default (`'room'`) for every dashed square; `[least]="0"` keeps the smallest. a square that is not dashed never changes.
package/README.md CHANGED
@@ -241,7 +241,8 @@ what a tap asks (`tap`: `stand` or `take-out`) and why it would be refused (`ref
241
241
  `inside-itself`, `given-place`, `last-place`, or the app's own key). `whereLevel(places, words.where)`
242
242
  makes context tiles of them (`zoom` on the deeper ones, which `<cube-context>` draws as a › that
243
243
  enters that path without asking `pick`). `insideOf`, `wouldLoop` and `refusalOf` are exported for an
244
- app's own drop sheet. the part is `where`, after move, before whose and hide.
244
+ app's own drop sheet. the part is `where`, after move, before whose and hide; tell (0.6.0) is last of
245
+ all, after the parts an app adds.
245
246
 
246
247
  ## the context cube
247
248
 
@@ -396,6 +397,38 @@ its own classes all start with `cc-` (`cc-tile`, `cc-icon`, …) and the square'
396
397
  app's global `.tile`, `.square` or `.place` rules do not reach inside; style it through the
397
398
  variables only.
398
399
 
400
+ ## telling us (since 0.6.0)
401
+
402
+ the kit a sheet of the app's own is built on, so anyone using mosaic, cubes or anlat can tell us
403
+ something (a bug, something to make better, an idea, or not known yet) into one pool, the owner's
404
+ todarch, through the backend's `POST /feedback`. headless like the context cube: it says no word and
405
+ draws no sheet. from mosaic's feedback capture (the study *feedback in every cube app*, 4 november
406
+ 2026).
407
+
408
+ ```ts
409
+ // app.config.ts
410
+ provideHttpClient(withInterceptors([authInterceptor, reportInterceptor])),
411
+ provideReportDiagnostics(),
412
+ { provide: REPORT_APP, useValue: { id: 'cubes', apiBaseUrl: environment.apiBaseUrl, build: BUILD_INFO } },
413
+ ```
414
+
415
+ - `REPORT_APP` (`ReportApp`): the app's id, the backend's address, its build, and optionally
416
+ `appOf(url)`, `screenOf(route, url)` (the app's own words or ids for a place, never a heading or a
417
+ name a person wrote), `environment()`, `draftKey` and `readsReports()` (the owner only).
418
+ - `ReportDiagnostics`, `ReportErrorHandler` (`provideReportDiagnostics()`) and `reportInterceptor`:
419
+ the last 5 errors and failed requests of 10 minutes, a request as its method, path and status (no
420
+ query), an error as its name and 300 characters.
421
+ - `reportContext()` / `ReportClient.context(held)`: app, screen, route pattern, the page's address
422
+ without query or fragment, environment and the held piece's key. `technicalLines` the context note.
423
+ - `ReportClient`: `capture({ kind, text, held })` sends it (and forgets the draft), `request(...)` is
424
+ what it would send, for "what goes with it", `justSaved(text)` a look before sending again
425
+ (`findJustSaved`; only where `readsReports()`: the backend itself answers the same words within 15
426
+ minutes with the same report), `undo(id)`, and `draft` (`ReportDraft`). `reportRefusal(error)` is a
427
+ 400's plain reason (20 a day, too long, an app not theirs), shown as it came.
428
+ - `tellLevel(words)`: the part tell, "tell us", the last of every context cube, after hide (selim, 4
429
+ november 2026): something broke, make it better, an idea, not known yet; words in
430
+ `ContextWords.tell`; icons `tell`, `bug`, `improvement`, `idea`, `not-known`.
431
+
399
432
  ## the square
400
433
 
401
434
  every face of a cube is drawn as a complete square of tiles (decision 23). `<cube-square>` is that
@@ -1,6 +1,9 @@
1
1
  import * as i0 from "@angular/core";
2
- import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef, InjectionToken, PLATFORM_ID, Renderer2, TemplateRef, afterNextRender, computed, contentChild, effect, inject, input, output, signal, untracked, viewChild } from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef, ErrorHandler, Injectable, InjectionToken, Injector, PLATFORM_ID, Renderer2, TemplateRef, afterNextRender, computed, contentChild, effect, inject, input, output, runInInjectionContext, signal, untracked, viewChild } from "@angular/core";
3
3
  import { NgTemplateOutlet, isPlatformBrowser } from "@angular/common";
4
+ import { HttpClient, HttpErrorResponse } from "@angular/common/http";
5
+ import { catchError, map, of, tap } from "rxjs";
6
+ import { Router } from "@angular/router";
4
7
  function sizeFor(count) {
5
8
  let s = 1;
6
9
  while (s * s * s < count && s < 7) s++;
@@ -1099,6 +1102,11 @@ const CUBE_ICON_NAMES = [
1099
1102
  "up",
1100
1103
  "down",
1101
1104
  "plus",
1105
+ "tell",
1106
+ "bug",
1107
+ "improvement",
1108
+ "idea",
1109
+ "not-known",
1102
1110
  "square",
1103
1111
  "one",
1104
1112
  "two",
@@ -1276,6 +1284,24 @@ var CubeIcon = class CubeIcon {
1276
1284
  <path d="M3 12s3.2-6 9-6 9 6 9 6-3.2 6-9 6-9-6-9-6Z" />
1277
1285
  <circle cx="12" cy="12" r="2.6" />
1278
1286
  }
1287
+ @case ('tell') {
1288
+ <path d="M5 4.5h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-7l-4.5 3.6v-3.6H5a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z" />
1289
+ }
1290
+ @case ('bug') {
1291
+ <path d="M5 5h14v14H5Z" />
1292
+ <path d="M12.5 5 10 9.5l3.5 3L11 19" />
1293
+ }
1294
+ @case ('improvement') {
1295
+ <path d="M4 17.5 9.5 12l3.5 3L20 8" />
1296
+ <path d="M15 8h5v5" />
1297
+ }
1298
+ @case ('idea') {
1299
+ <path d="M9.5 17.5h5M10.5 20.5h3" />
1300
+ <path d="M12 3.5a6 6 0 0 0-3.6 10.8c.7.5 1.1 1.3 1.1 2.1v.1h5v-.1c0-.8.4-1.6 1.1-2.1A6 6 0 0 0 12 3.5Z" />
1301
+ }
1302
+ @case ('not-known') {
1303
+ <circle cx="12" cy="12" r="8" stroke-dasharray="2.6 2.6" />
1304
+ }
1279
1305
  @case ('hidden') {
1280
1306
  <path d="M3 12s3.2-6 9-6c1.6 0 3 .45 4.2 1.1M21 12s-3.2 6-9 6c-1.6 0-3-.45-4.2-1.1" />
1281
1307
  <path d="M4.5 19.5 19.5 4.5" />
@@ -1383,6 +1409,24 @@ i0.ɵɵngDeclareClassMetadata({
1383
1409
  <path d="M3 12s3.2-6 9-6 9 6 9 6-3.2 6-9 6-9-6-9-6Z" />
1384
1410
  <circle cx="12" cy="12" r="2.6" />
1385
1411
  }
1412
+ @case ('tell') {
1413
+ <path d="M5 4.5h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-7l-4.5 3.6v-3.6H5a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z" />
1414
+ }
1415
+ @case ('bug') {
1416
+ <path d="M5 5h14v14H5Z" />
1417
+ <path d="M12.5 5 10 9.5l3.5 3L11 19" />
1418
+ }
1419
+ @case ('improvement') {
1420
+ <path d="M4 17.5 9.5 12l3.5 3L20 8" />
1421
+ <path d="M15 8h5v5" />
1422
+ }
1423
+ @case ('idea') {
1424
+ <path d="M9.5 17.5h5M10.5 20.5h3" />
1425
+ <path d="M12 3.5a6 6 0 0 0-3.6 10.8c.7.5 1.1 1.3 1.1 2.1v.1h5v-.1c0-.8.4-1.6 1.1-2.1A6 6 0 0 0 12 3.5Z" />
1426
+ }
1427
+ @case ('not-known') {
1428
+ <circle cx="12" cy="12" r="8" stroke-dasharray="2.6 2.6" />
1429
+ }
1386
1430
  @case ('hidden') {
1387
1431
  <path d="M3 12s3.2-6 9-6c1.6 0 3 .45 4.2 1.1M21 12s-3.2 6-9 6c-1.6 0-3-.45-4.2-1.1" />
1388
1432
  <path d="M4.5 19.5 19.5 4.5" />
@@ -1424,11 +1468,20 @@ const CONTEXT_PART_ORDER = [
1424
1468
  "move",
1425
1469
  "where",
1426
1470
  "whose",
1427
- "hide"
1471
+ "hide",
1472
+ "tell"
1428
1473
  ];
1474
+ const CONTEXT_LAST_PARTS = ["tell"];
1429
1475
  function orderParts(parts) {
1430
1476
  const unique = [...new Set(parts)];
1431
- return [...CONTEXT_PART_ORDER.filter((p) => unique.includes(p)), ...unique.filter((p) => !CONTEXT_PART_ORDER.includes(p))];
1477
+ const known = CONTEXT_PART_ORDER.filter((p) => unique.includes(p) && !CONTEXT_LAST_PARTS.includes(p));
1478
+ const own = unique.filter((p) => !CONTEXT_PART_ORDER.includes(p));
1479
+ const last = CONTEXT_LAST_PARTS.filter((p) => unique.includes(p));
1480
+ return [
1481
+ ...known,
1482
+ ...own,
1483
+ ...last
1484
+ ];
1432
1485
  }
1433
1486
  const CONTEXT_CUBE_SOURCE = new InjectionToken("CONTEXT_CUBE_SOURCE");
1434
1487
  const CONTEXT_CUBE_WORDS = new InjectionToken("CONTEXT_CUBE_WORDS");
@@ -1646,6 +1699,22 @@ function addLevel(ways) {
1646
1699
  });
1647
1700
  return [...ways.filter((w) => !w.adds), ...ways.filter((w) => w.adds)].map(tile);
1648
1701
  }
1702
+ function tellLevel(words) {
1703
+ const w = words.tell;
1704
+ const tile = (key, data, word, icon) => ({
1705
+ key: "tell:" + key,
1706
+ title: word?.title ?? "",
1707
+ line: word?.line ?? null,
1708
+ icon,
1709
+ data
1710
+ });
1711
+ return [
1712
+ tile("bug", "bug", w?.bug, "bug"),
1713
+ tile("improvement", "improvement", w?.improvement, "improvement"),
1714
+ tile("idea", "idea", w?.idea, "idea"),
1715
+ tile("not-known", null, w?.notKnown, "not-known")
1716
+ ];
1717
+ }
1649
1718
  function insideOf(source, piece) {
1650
1719
  const seen = /* @__PURE__ */ new Set();
1651
1720
  const walk = [...source.partsOf(piece)];
@@ -1851,6 +1920,12 @@ function squareLevelFor(cell) {
1851
1920
  function squareGap(n) {
1852
1921
  return n <= 2 ? 12 : n === 3 ? 10 : n === 4 ? 8 : 5;
1853
1922
  }
1923
+ const SQUARE_ROOM_BELOW = 16;
1924
+ const SQUARE_LEAST_TALL = 320;
1925
+ function squareTall(windowHeight, top, opts = {}) {
1926
+ const { below = 16, least = 320 } = opts;
1927
+ return Math.max(least, Math.floor(windowHeight - (top > 0 ? top : 0) - below));
1928
+ }
1854
1929
  const SQUARE_MAX_SIDE = 880;
1855
1930
  const SQUARE_LIST_ROW = 76;
1856
1931
  const SQUARE_LEAST_CELL = 200;
@@ -2180,13 +2255,19 @@ var CubeSquare = class CubeSquare {
2180
2255
  if (this.rings()) untracked(() => this.timers.push(setTimeout(() => this.nudge())));
2181
2256
  });
2182
2257
  afterNextRender(() => {
2183
- const measure = () => this.room.set({
2184
- width: host.clientWidth,
2185
- tall: Math.max(320, window.innerHeight - 48)
2186
- });
2258
+ const measure = () => {
2259
+ const top = host.getBoundingClientRect().top + window.scrollY;
2260
+ const next = {
2261
+ width: host.clientWidth,
2262
+ tall: squareTall(window.innerHeight, top)
2263
+ };
2264
+ const now = this.room();
2265
+ if (now.width !== next.width || now.tall !== next.tall) this.room.set(next);
2266
+ };
2187
2267
  measure();
2188
2268
  const watch = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
2189
2269
  watch?.observe(host);
2270
+ watch?.observe(document.body);
2190
2271
  window.addEventListener("resize", measure);
2191
2272
  destroy.onDestroy(() => {
2192
2273
  watch?.disconnect();
@@ -3424,6 +3505,265 @@ i0.ɵɵngDeclareClassMetadata({
3424
3505
  }]
3425
3506
  }
3426
3507
  });
3427
- export { CONTEXT_CUBE_SOURCE, CONTEXT_CUBE_WORDS, CONTEXT_PART_ORDER, CUBE_CARRY_CLASSES, CUBE_CARRY_MOVE, CUBE_DROP_OUT, CUBE_HOLD_MS, CUBE_HOLD_SLOP, CUBE_ICON_NAMES, CUBE_LOADING_COLOURS, CUBE_LOADING_LEVELS, CUBE_LOADING_STEP_MS, CUBE_LOADING_STICKERS, CUBE_LOADING_WAYS, CUBE_LOADING_WIDTH, CUBE_PINCH_APART, CUBE_PINCH_TOGETHER, CUBE_WHEEL_QUIET_MS, CubeCarry, CubeContext, CubeHold, CubeIcon, CubeLoading, CubePicture, CubePinch, CubeSquare, CubeSquareTile, MINE_SORT, PART_ICONS, SQUARE_LEAST_CELL, SQUARE_LIST_ROW, SQUARE_MAX_SIDE, TEXT_ORDERS, TURNING_BELOW, TURNING_NUDGE_UNTIL, TURNING_SIDES, VIEW_KEYS, addLevel, bringLevel, bringOf, contextIcon, contextLevelFor, contextSquare, cubeIconCells, directionWords, drawCube, gridFor, hideLevel, insideOf, isView, loadingStickers, loadingWayFor, moveBefore, namesLevel, orderLevel, orderOf, orderParts, partsLevel, placesOf, refusalOf, sizeFor, sortDirectionsLevel, sortItems, sortLevel, squareGap, squareLayout, squareLeast, squareLevelFor, startsWheelGesture, stillStep, turningFront, turningRows, viewLevel, whereLevel, wouldLoop };
3508
+ const REPORT_KINDS = [
3509
+ "bug",
3510
+ "improvement",
3511
+ "idea"
3512
+ ];
3513
+ const REPORT_APP = new InjectionToken("REPORT_APP");
3514
+ const REPORT_INCIDENTS_KEPT = 5;
3515
+ const REPORT_INCIDENTS_MS = 6e5;
3516
+ const ERROR_CHARS = 300;
3517
+ function scrubbedPath(url) {
3518
+ return url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, "").replace(/[?#].*$/, "") || "/";
3519
+ }
3520
+ function errorLine(error) {
3521
+ return `error: ${(error instanceof Error ? `${error.name}: ${error.message}` : String(error ?? "unknown error")).slice(0, ERROR_CHARS)}`;
3522
+ }
3523
+ var ReportDiagnostics = class ReportDiagnostics {
3524
+ incidents = [];
3525
+ constructor() {
3526
+ if (typeof window === "undefined") return;
3527
+ window.addEventListener("error", (e) => this.recordError(e.error ?? e.message));
3528
+ window.addEventListener("unhandledrejection", (e) => this.recordError(e.reason));
3529
+ }
3530
+ recordError(error) {
3531
+ this.add(errorLine(error));
3532
+ }
3533
+ recordFailedRequest(method, url, status) {
3534
+ this.add(`${method} ${scrubbedPath(url)} -> ${status || "no response"}`);
3535
+ }
3536
+ recent(now = Date.now()) {
3537
+ const since = now - REPORT_INCIDENTS_MS;
3538
+ return this.incidents.filter((i) => i.at >= since).map((i) => i.text);
3539
+ }
3540
+ add(text) {
3541
+ this.incidents = [...this.incidents, {
3542
+ at: Date.now(),
3543
+ text
3544
+ }].slice(-5);
3545
+ }
3546
+ static ɵfac = i0.ɵɵngDeclareFactory({
3547
+ minVersion: "12.0.0",
3548
+ version: "22.2.4",
3549
+ ngImport: i0,
3550
+ type: ReportDiagnostics,
3551
+ deps: [],
3552
+ target: i0.ɵɵFactoryTarget.Injectable
3553
+ });
3554
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3555
+ minVersion: "12.0.0",
3556
+ version: "22.2.4",
3557
+ ngImport: i0,
3558
+ type: ReportDiagnostics,
3559
+ providedIn: "root"
3560
+ });
3561
+ };
3562
+ i0.ɵɵngDeclareClassMetadata({
3563
+ minVersion: "12.0.0",
3564
+ version: "22.2.4",
3565
+ ngImport: i0,
3566
+ type: ReportDiagnostics,
3567
+ decorators: [{
3568
+ type: Injectable,
3569
+ args: [{ providedIn: "root" }]
3570
+ }],
3571
+ ctorParameters: () => []
3572
+ });
3573
+ var ReportErrorHandler = class ReportErrorHandler extends ErrorHandler {
3574
+ diagnostics = inject(ReportDiagnostics);
3575
+ handleError(error) {
3576
+ this.diagnostics.recordError(error);
3577
+ super.handleError(error);
3578
+ }
3579
+ static ɵfac = i0.ɵɵngDeclareFactory({
3580
+ minVersion: "12.0.0",
3581
+ version: "22.2.4",
3582
+ ngImport: i0,
3583
+ type: ReportErrorHandler,
3584
+ deps: null,
3585
+ target: i0.ɵɵFactoryTarget.Injectable
3586
+ });
3587
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3588
+ minVersion: "12.0.0",
3589
+ version: "22.2.4",
3590
+ ngImport: i0,
3591
+ type: ReportErrorHandler
3592
+ });
3593
+ };
3594
+ i0.ɵɵngDeclareClassMetadata({
3595
+ minVersion: "12.0.0",
3596
+ version: "22.2.4",
3597
+ ngImport: i0,
3598
+ type: ReportErrorHandler,
3599
+ decorators: [{ type: Injectable }]
3600
+ });
3601
+ const reportInterceptor = (req, next) => {
3602
+ const diagnostics = inject(ReportDiagnostics);
3603
+ return next(req).pipe(tap({ error: (error) => {
3604
+ if (error instanceof HttpErrorResponse && !/\/feedback(\/|$|\?)/.test(req.url)) diagnostics.recordFailedRequest(req.method, req.url, error.status);
3605
+ } }));
3606
+ };
3607
+ function provideReportDiagnostics() {
3608
+ return [{
3609
+ provide: ErrorHandler,
3610
+ useClass: ReportErrorHandler
3611
+ }];
3612
+ }
3613
+ function routePattern(root) {
3614
+ const parts = [];
3615
+ let route = root;
3616
+ while (route) {
3617
+ const path = route.routeConfig?.path;
3618
+ if (path) parts.push(path);
3619
+ route = route.firstChild;
3620
+ }
3621
+ return parts.join("/");
3622
+ }
3623
+ function environmentOf(host) {
3624
+ if (/^(localhost|127\.0\.0\.1|\[::1\])(:|$)/.test(host)) return "local";
3625
+ if (/^dev(--|\.)/.test(host)) return "dev";
3626
+ return "prod";
3627
+ }
3628
+ function contextOf(app, at, held = null) {
3629
+ const path = at.url.replace(/[?#].*$/, "") || "/";
3630
+ return {
3631
+ app: app.appOf?.(path) ?? app.id,
3632
+ screen: app.screenOf?.(at.route, path) ?? (at.route || "/"),
3633
+ route: at.route,
3634
+ pageUrl: at.origin + (at.pathname ?? path),
3635
+ environment: app.environment?.() ?? environmentOf(at.host),
3636
+ held
3637
+ };
3638
+ }
3639
+ function reportContext(opts = {}) {
3640
+ const app = inject(REPORT_APP);
3641
+ const router = inject(Router);
3642
+ return contextOf(app, {
3643
+ url: router.url,
3644
+ route: routePattern(router.routerState.snapshot.root),
3645
+ origin: typeof location === "undefined" ? "" : location.origin,
3646
+ host: typeof location === "undefined" ? "" : location.host,
3647
+ pathname: typeof location === "undefined" ? void 0 : location.pathname
3648
+ }, opts.held ?? null);
3649
+ }
3650
+ function technicalLines(input) {
3651
+ const lines = [];
3652
+ if (input.held) lines.push(`held ${input.held}`);
3653
+ const b = input.build;
3654
+ if (b?.commitShort) lines.push(`build ${b.commitShort} (${b.branch ?? "?"}, ${b.builtAt ?? "?"})`);
3655
+ if (input.size) lines.push(`screen ${input.size.width}x${input.size.height}${input.agent ? ", " + input.agent : ""}`);
3656
+ return [...lines, ...input.incidents ?? []];
3657
+ }
3658
+ const JUST_SAVED_MINUTES = 15;
3659
+ function findJustSaved(reports, detail, now = /* @__PURE__ */ new Date()) {
3660
+ const text = detail.trim();
3661
+ const since = now.getTime() - 9e5;
3662
+ return reports.find((r) => (r.detail ?? "").trim() === text && !!r.reported && new Date(r.reported).getTime() >= since);
3663
+ }
3664
+ var ReportDraft = class {
3665
+ key;
3666
+ constructor(key) {
3667
+ this.key = key;
3668
+ }
3669
+ read() {
3670
+ try {
3671
+ return localStorage.getItem(this.key) ?? "";
3672
+ } catch {
3673
+ return "";
3674
+ }
3675
+ }
3676
+ write(text) {
3677
+ try {
3678
+ if (text.trim()) localStorage.setItem(this.key, text);
3679
+ else localStorage.removeItem(this.key);
3680
+ } catch {}
3681
+ }
3682
+ clear() {
3683
+ this.write("");
3684
+ }
3685
+ };
3686
+ function reportRefusal(error) {
3687
+ if (!(error instanceof HttpErrorResponse) || error.status !== 400) return null;
3688
+ const body = error.error;
3689
+ if (typeof body === "string") return body.trim() || null;
3690
+ const said = body?.message;
3691
+ return typeof said === "string" && said.trim() ? said.trim() : null;
3692
+ }
3693
+ function reportRequest(input, ctx, technical) {
3694
+ return {
3695
+ ...input.kind ? { type: input.kind } : {},
3696
+ detail: input.text.trim(),
3697
+ app: ctx.app,
3698
+ screen: ctx.screen,
3699
+ pageUrl: ctx.pageUrl,
3700
+ route: ctx.route,
3701
+ environment: ctx.environment,
3702
+ context: technical.join("\n")
3703
+ };
3704
+ }
3705
+ var ReportClient = class ReportClient {
3706
+ http = inject(HttpClient);
3707
+ app = inject(REPORT_APP);
3708
+ diagnostics = inject(ReportDiagnostics);
3709
+ injector = inject(Injector);
3710
+ url = `${this.app.apiBaseUrl.replace(/\/$/, "")}/feedback`;
3711
+ draft = new ReportDraft(this.app.draftKey ?? `${this.app.id}.report.draft`);
3712
+ context(held = null) {
3713
+ return runInInjectionContext(this.injector, () => reportContext({ held }));
3714
+ }
3715
+ incidents() {
3716
+ return this.diagnostics.recent();
3717
+ }
3718
+ request(input, ctx = this.context(input.held ?? null)) {
3719
+ const withIncidents = input.withIncidents ?? input.kind === "bug";
3720
+ return reportRequest(input, ctx, technicalLines({
3721
+ held: input.held ?? ctx.held,
3722
+ build: this.app.build,
3723
+ size: typeof window === "undefined" ? null : {
3724
+ width: window.innerWidth,
3725
+ height: window.innerHeight
3726
+ },
3727
+ agent: typeof navigator === "undefined" ? null : navigator.userAgent,
3728
+ incidents: withIncidents ? this.incidents() : []
3729
+ }));
3730
+ }
3731
+ capture(input, ctx) {
3732
+ return this.http.post(this.url, this.request(input, ctx)).pipe(map((saved) => (this.draft.clear(), saved)));
3733
+ }
3734
+ justSaved(text) {
3735
+ if (!this.app.readsReports?.()) return of(null);
3736
+ return this.http.get(this.url).pipe(map((reports) => findJustSaved(reports, text) ?? null), catchError(() => of(null)));
3737
+ }
3738
+ undo(id) {
3739
+ return this.http.delete(`${this.url}/${encodeURIComponent(id)}`);
3740
+ }
3741
+ static ɵfac = i0.ɵɵngDeclareFactory({
3742
+ minVersion: "12.0.0",
3743
+ version: "22.2.4",
3744
+ ngImport: i0,
3745
+ type: ReportClient,
3746
+ deps: [],
3747
+ target: i0.ɵɵFactoryTarget.Injectable
3748
+ });
3749
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3750
+ minVersion: "12.0.0",
3751
+ version: "22.2.4",
3752
+ ngImport: i0,
3753
+ type: ReportClient,
3754
+ providedIn: "root"
3755
+ });
3756
+ };
3757
+ i0.ɵɵngDeclareClassMetadata({
3758
+ minVersion: "12.0.0",
3759
+ version: "22.2.4",
3760
+ ngImport: i0,
3761
+ type: ReportClient,
3762
+ decorators: [{
3763
+ type: Injectable,
3764
+ args: [{ providedIn: "root" }]
3765
+ }]
3766
+ });
3767
+ export { CONTEXT_CUBE_SOURCE, CONTEXT_CUBE_WORDS, CONTEXT_LAST_PARTS, CONTEXT_PART_ORDER, CUBE_CARRY_CLASSES, CUBE_CARRY_MOVE, CUBE_DROP_OUT, CUBE_HOLD_MS, CUBE_HOLD_SLOP, CUBE_ICON_NAMES, CUBE_LOADING_COLOURS, CUBE_LOADING_LEVELS, CUBE_LOADING_STEP_MS, CUBE_LOADING_STICKERS, CUBE_LOADING_WAYS, CUBE_LOADING_WIDTH, CUBE_PINCH_APART, CUBE_PINCH_TOGETHER, CUBE_WHEEL_QUIET_MS, CubeCarry, CubeContext, CubeHold, CubeIcon, CubeLoading, CubePicture, CubePinch, CubeSquare, CubeSquareTile, JUST_SAVED_MINUTES, MINE_SORT, PART_ICONS, REPORT_APP, REPORT_INCIDENTS_KEPT, REPORT_INCIDENTS_MS, REPORT_KINDS, ReportClient, ReportDiagnostics, ReportDraft, ReportErrorHandler, SQUARE_LEAST_CELL, SQUARE_LEAST_TALL, SQUARE_LIST_ROW, SQUARE_MAX_SIDE, SQUARE_ROOM_BELOW, TEXT_ORDERS, TURNING_BELOW, TURNING_NUDGE_UNTIL, TURNING_SIDES, VIEW_KEYS, addLevel, bringLevel, bringOf, contextIcon, contextLevelFor, contextOf, contextSquare, cubeIconCells, directionWords, drawCube, environmentOf, errorLine, findJustSaved, gridFor, hideLevel, insideOf, isView, loadingStickers, loadingWayFor, moveBefore, namesLevel, orderLevel, orderOf, orderParts, partsLevel, placesOf, provideReportDiagnostics, refusalOf, reportContext, reportInterceptor, reportRefusal, reportRequest, routePattern, scrubbedPath, sizeFor, sortDirectionsLevel, sortItems, sortLevel, squareGap, squareLayout, squareLeast, squareLevelFor, squareTall, startsWheelGesture, stillStep, technicalLines, tellLevel, turningFront, turningRows, viewLevel, whereLevel, wouldLoop };
3428
3768
 
3429
3769
  //# sourceMappingURL=selimssevgi-cube-design-angular.mjs.map