@selimssevgi/cube-design-angular 0.5.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@
4
4
 
5
5
  nothing yet.
6
6
 
7
+ ## 0.6.0 (proposed, 4 november 2026: the report kit, so mosaic, cubes and anlat let anyone tell us something)
8
+
9
+ - **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.
10
+ - **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.
11
+ - `@angular/router` is a peer dependency now (`reportContext` reads the route pattern); every app already has it.
12
+
13
+ ## 0.5.1 (4 november 2026: an empty piece has room)
14
+
15
+ - **`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.
16
+
7
17
  ## 0.5.0 (4 november 2026: released so mosaic's home grouped my way brings things in and keeps my own order)
8
18
 
9
19
  - **bring things in**, `bringOf(source, into, { at, pieces })` and `bringLevel` (`CubeBring`, `BringOptions`, `BringWords`): where turned round (the study `studies/bringing-things-in.md`, way b, picked by selim on 4 november 2026). where lists places for one piece; bring lists pieces for one place: what stands in `at` (the top by default), each ticked when it is in already, the place itself and a piece holding it pale, a › on a piece with something to bring inside it, what a tap asks (`stand`, `take-out`) and why it would be refused, over the same `WhereSource`. `pieces` lists the app's own (lately) instead. from mosaic's grouping prototype (`bring-things-in.html`).
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)];
@@ -1853,11 +1922,18 @@ function squareGap(n) {
1853
1922
  }
1854
1923
  const SQUARE_MAX_SIDE = 880;
1855
1924
  const SQUARE_LIST_ROW = 76;
1925
+ const SQUARE_LEAST_CELL = 200;
1926
+ function squareLeast(side, opts = {}) {
1927
+ const { cell = 200, min = 3, max = 5 } = opts;
1928
+ if (!(side > 0)) return min;
1929
+ return Math.min(max, Math.max(min, Math.ceil(side / cell)));
1930
+ }
1856
1931
  function squareLayout(view, count, room, opts = {}) {
1857
1932
  const n = Math.max(0, count);
1858
1933
  const grid = gridFor(view, n);
1859
- const cols = grid.cols;
1860
- const rows = view === "square" ? grid.rows : Math.max(1, Math.ceil(n / cols));
1934
+ const least = view === "square" ? Math.max(0, Math.floor(opts.least ?? 0)) : 0;
1935
+ const cols = Math.max(grid.cols, least);
1936
+ const rows = view === "square" ? Math.max(grid.rows, least) : Math.max(1, Math.ceil(n / cols));
1861
1937
  const width = Math.max(0, Math.min(view === "square" ? Math.min(room.width, room.tall) : room.width, opts.maxSide ?? 880));
1862
1938
  const list = view === "one";
1863
1939
  const gap = list ? 8 : squareGap(Math.max(cols, Math.min(rows, 5)));
@@ -1964,6 +2040,7 @@ var CubeSquare = class CubeSquare {
1964
2040
  maxSide = input(880, ...ngDevMode ? [{ debugName: "maxSide" }] : /* istanbul ignore next */ []);
1965
2041
  listRow = input(76, ...ngDevMode ? [{ debugName: "listRow" }] : /* istanbul ignore next */ []);
1966
2042
  dashed = input(false, ...ngDevMode ? [{ debugName: "dashed" }] : /* istanbul ignore next */ []);
2043
+ least = input("room", ...ngDevMode ? [{ debugName: "least" }] : /* istanbul ignore next */ []);
1967
2044
  keyOf = input((_, i) => i, ...ngDevMode ? [{ debugName: "keyOf" }] : /* istanbul ignore next */ []);
1968
2045
  turning = input(false, ...ngDevMode ? [{ debugName: "turning" }] : /* istanbul ignore next */ []);
1969
2046
  turnBelow = input(640, ...ngDevMode ? [{ debugName: "turnBelow" }] : /* istanbul ignore next */ []);
@@ -1989,9 +2066,12 @@ var CubeSquare = class CubeSquare {
1989
2066
  width,
1990
2067
  tall: r.tall || width
1991
2068
  };
2069
+ const least = this.least();
2070
+ const side = Math.min(width, room.tall, this.maxSide());
1992
2071
  const opts = {
1993
2072
  maxSide: this.maxSide(),
1994
- listRow: this.listRow()
2073
+ listRow: this.listRow(),
2074
+ least: !this.dashed() ? 0 : least === "room" ? squareLeast(side) : least
1995
2075
  };
1996
2076
  const front = this.front();
1997
2077
  if (!front) return squareLayout(this.view(), this.tiles().length, room, opts);
@@ -2246,6 +2326,13 @@ var CubeSquare = class CubeSquare {
2246
2326
  isRequired: false,
2247
2327
  transformFunction: null
2248
2328
  },
2329
+ least: {
2330
+ classPropertyName: "least",
2331
+ publicName: "least",
2332
+ isSignal: true,
2333
+ isRequired: false,
2334
+ transformFunction: null
2335
+ },
2249
2336
  keyOf: {
2250
2337
  classPropertyName: "keyOf",
2251
2338
  publicName: "keyOf",
@@ -2591,6 +2678,14 @@ i0.ɵɵngDeclareClassMetadata({
2591
2678
  required: false
2592
2679
  }]
2593
2680
  }],
2681
+ least: [{
2682
+ type: i0.Input,
2683
+ args: [{
2684
+ isSignal: true,
2685
+ alias: "least",
2686
+ required: false
2687
+ }]
2688
+ }],
2594
2689
  keyOf: [{
2595
2690
  type: i0.Input,
2596
2691
  args: [{
@@ -2946,6 +3041,7 @@ var CubeContext = class CubeContext {
2946
3041
  "maxSide",
2947
3042
  "listRow",
2948
3043
  "dashed",
3044
+ "least",
2949
3045
  "keyOf",
2950
3046
  "turning",
2951
3047
  "turnBelow",
@@ -3289,6 +3385,7 @@ var CubeLoading = class CubeLoading {
3289
3385
  "maxSide",
3290
3386
  "listRow",
3291
3387
  "dashed",
3388
+ "least",
3292
3389
  "keyOf",
3293
3390
  "turning",
3294
3391
  "turnBelow",
@@ -3396,6 +3493,265 @@ i0.ɵɵngDeclareClassMetadata({
3396
3493
  }]
3397
3494
  }
3398
3495
  });
3399
- 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_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, squareLevelFor, startsWheelGesture, stillStep, turningFront, turningRows, viewLevel, whereLevel, wouldLoop };
3496
+ const REPORT_KINDS = [
3497
+ "bug",
3498
+ "improvement",
3499
+ "idea"
3500
+ ];
3501
+ const REPORT_APP = new InjectionToken("REPORT_APP");
3502
+ const REPORT_INCIDENTS_KEPT = 5;
3503
+ const REPORT_INCIDENTS_MS = 6e5;
3504
+ const ERROR_CHARS = 300;
3505
+ function scrubbedPath(url) {
3506
+ return url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, "").replace(/[?#].*$/, "") || "/";
3507
+ }
3508
+ function errorLine(error) {
3509
+ return `error: ${(error instanceof Error ? `${error.name}: ${error.message}` : String(error ?? "unknown error")).slice(0, ERROR_CHARS)}`;
3510
+ }
3511
+ var ReportDiagnostics = class ReportDiagnostics {
3512
+ incidents = [];
3513
+ constructor() {
3514
+ if (typeof window === "undefined") return;
3515
+ window.addEventListener("error", (e) => this.recordError(e.error ?? e.message));
3516
+ window.addEventListener("unhandledrejection", (e) => this.recordError(e.reason));
3517
+ }
3518
+ recordError(error) {
3519
+ this.add(errorLine(error));
3520
+ }
3521
+ recordFailedRequest(method, url, status) {
3522
+ this.add(`${method} ${scrubbedPath(url)} -> ${status || "no response"}`);
3523
+ }
3524
+ recent(now = Date.now()) {
3525
+ const since = now - REPORT_INCIDENTS_MS;
3526
+ return this.incidents.filter((i) => i.at >= since).map((i) => i.text);
3527
+ }
3528
+ add(text) {
3529
+ this.incidents = [...this.incidents, {
3530
+ at: Date.now(),
3531
+ text
3532
+ }].slice(-5);
3533
+ }
3534
+ static ɵfac = i0.ɵɵngDeclareFactory({
3535
+ minVersion: "12.0.0",
3536
+ version: "22.2.4",
3537
+ ngImport: i0,
3538
+ type: ReportDiagnostics,
3539
+ deps: [],
3540
+ target: i0.ɵɵFactoryTarget.Injectable
3541
+ });
3542
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3543
+ minVersion: "12.0.0",
3544
+ version: "22.2.4",
3545
+ ngImport: i0,
3546
+ type: ReportDiagnostics,
3547
+ providedIn: "root"
3548
+ });
3549
+ };
3550
+ i0.ɵɵngDeclareClassMetadata({
3551
+ minVersion: "12.0.0",
3552
+ version: "22.2.4",
3553
+ ngImport: i0,
3554
+ type: ReportDiagnostics,
3555
+ decorators: [{
3556
+ type: Injectable,
3557
+ args: [{ providedIn: "root" }]
3558
+ }],
3559
+ ctorParameters: () => []
3560
+ });
3561
+ var ReportErrorHandler = class ReportErrorHandler extends ErrorHandler {
3562
+ diagnostics = inject(ReportDiagnostics);
3563
+ handleError(error) {
3564
+ this.diagnostics.recordError(error);
3565
+ super.handleError(error);
3566
+ }
3567
+ static ɵfac = i0.ɵɵngDeclareFactory({
3568
+ minVersion: "12.0.0",
3569
+ version: "22.2.4",
3570
+ ngImport: i0,
3571
+ type: ReportErrorHandler,
3572
+ deps: null,
3573
+ target: i0.ɵɵFactoryTarget.Injectable
3574
+ });
3575
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3576
+ minVersion: "12.0.0",
3577
+ version: "22.2.4",
3578
+ ngImport: i0,
3579
+ type: ReportErrorHandler
3580
+ });
3581
+ };
3582
+ i0.ɵɵngDeclareClassMetadata({
3583
+ minVersion: "12.0.0",
3584
+ version: "22.2.4",
3585
+ ngImport: i0,
3586
+ type: ReportErrorHandler,
3587
+ decorators: [{ type: Injectable }]
3588
+ });
3589
+ const reportInterceptor = (req, next) => {
3590
+ const diagnostics = inject(ReportDiagnostics);
3591
+ return next(req).pipe(tap({ error: (error) => {
3592
+ if (error instanceof HttpErrorResponse && !/\/feedback(\/|$|\?)/.test(req.url)) diagnostics.recordFailedRequest(req.method, req.url, error.status);
3593
+ } }));
3594
+ };
3595
+ function provideReportDiagnostics() {
3596
+ return [{
3597
+ provide: ErrorHandler,
3598
+ useClass: ReportErrorHandler
3599
+ }];
3600
+ }
3601
+ function routePattern(root) {
3602
+ const parts = [];
3603
+ let route = root;
3604
+ while (route) {
3605
+ const path = route.routeConfig?.path;
3606
+ if (path) parts.push(path);
3607
+ route = route.firstChild;
3608
+ }
3609
+ return parts.join("/");
3610
+ }
3611
+ function environmentOf(host) {
3612
+ if (/^(localhost|127\.0\.0\.1|\[::1\])(:|$)/.test(host)) return "local";
3613
+ if (/^dev(--|\.)/.test(host)) return "dev";
3614
+ return "prod";
3615
+ }
3616
+ function contextOf(app, at, held = null) {
3617
+ const path = at.url.replace(/[?#].*$/, "") || "/";
3618
+ return {
3619
+ app: app.appOf?.(path) ?? app.id,
3620
+ screen: app.screenOf?.(at.route, path) ?? (at.route || "/"),
3621
+ route: at.route,
3622
+ pageUrl: at.origin + (at.pathname ?? path),
3623
+ environment: app.environment?.() ?? environmentOf(at.host),
3624
+ held
3625
+ };
3626
+ }
3627
+ function reportContext(opts = {}) {
3628
+ const app = inject(REPORT_APP);
3629
+ const router = inject(Router);
3630
+ return contextOf(app, {
3631
+ url: router.url,
3632
+ route: routePattern(router.routerState.snapshot.root),
3633
+ origin: typeof location === "undefined" ? "" : location.origin,
3634
+ host: typeof location === "undefined" ? "" : location.host,
3635
+ pathname: typeof location === "undefined" ? void 0 : location.pathname
3636
+ }, opts.held ?? null);
3637
+ }
3638
+ function technicalLines(input) {
3639
+ const lines = [];
3640
+ if (input.held) lines.push(`held ${input.held}`);
3641
+ const b = input.build;
3642
+ if (b?.commitShort) lines.push(`build ${b.commitShort} (${b.branch ?? "?"}, ${b.builtAt ?? "?"})`);
3643
+ if (input.size) lines.push(`screen ${input.size.width}x${input.size.height}${input.agent ? ", " + input.agent : ""}`);
3644
+ return [...lines, ...input.incidents ?? []];
3645
+ }
3646
+ const JUST_SAVED_MINUTES = 15;
3647
+ function findJustSaved(reports, detail, now = /* @__PURE__ */ new Date()) {
3648
+ const text = detail.trim();
3649
+ const since = now.getTime() - 9e5;
3650
+ return reports.find((r) => (r.detail ?? "").trim() === text && !!r.reported && new Date(r.reported).getTime() >= since);
3651
+ }
3652
+ var ReportDraft = class {
3653
+ key;
3654
+ constructor(key) {
3655
+ this.key = key;
3656
+ }
3657
+ read() {
3658
+ try {
3659
+ return localStorage.getItem(this.key) ?? "";
3660
+ } catch {
3661
+ return "";
3662
+ }
3663
+ }
3664
+ write(text) {
3665
+ try {
3666
+ if (text.trim()) localStorage.setItem(this.key, text);
3667
+ else localStorage.removeItem(this.key);
3668
+ } catch {}
3669
+ }
3670
+ clear() {
3671
+ this.write("");
3672
+ }
3673
+ };
3674
+ function reportRefusal(error) {
3675
+ if (!(error instanceof HttpErrorResponse) || error.status !== 400) return null;
3676
+ const body = error.error;
3677
+ if (typeof body === "string") return body.trim() || null;
3678
+ const said = body?.message;
3679
+ return typeof said === "string" && said.trim() ? said.trim() : null;
3680
+ }
3681
+ function reportRequest(input, ctx, technical) {
3682
+ return {
3683
+ ...input.kind ? { type: input.kind } : {},
3684
+ detail: input.text.trim(),
3685
+ app: ctx.app,
3686
+ screen: ctx.screen,
3687
+ pageUrl: ctx.pageUrl,
3688
+ route: ctx.route,
3689
+ environment: ctx.environment,
3690
+ context: technical.join("\n")
3691
+ };
3692
+ }
3693
+ var ReportClient = class ReportClient {
3694
+ http = inject(HttpClient);
3695
+ app = inject(REPORT_APP);
3696
+ diagnostics = inject(ReportDiagnostics);
3697
+ injector = inject(Injector);
3698
+ url = `${this.app.apiBaseUrl.replace(/\/$/, "")}/feedback`;
3699
+ draft = new ReportDraft(this.app.draftKey ?? `${this.app.id}.report.draft`);
3700
+ context(held = null) {
3701
+ return runInInjectionContext(this.injector, () => reportContext({ held }));
3702
+ }
3703
+ incidents() {
3704
+ return this.diagnostics.recent();
3705
+ }
3706
+ request(input, ctx = this.context(input.held ?? null)) {
3707
+ const withIncidents = input.withIncidents ?? input.kind === "bug";
3708
+ return reportRequest(input, ctx, technicalLines({
3709
+ held: input.held ?? ctx.held,
3710
+ build: this.app.build,
3711
+ size: typeof window === "undefined" ? null : {
3712
+ width: window.innerWidth,
3713
+ height: window.innerHeight
3714
+ },
3715
+ agent: typeof navigator === "undefined" ? null : navigator.userAgent,
3716
+ incidents: withIncidents ? this.incidents() : []
3717
+ }));
3718
+ }
3719
+ capture(input, ctx) {
3720
+ return this.http.post(this.url, this.request(input, ctx)).pipe(map((saved) => (this.draft.clear(), saved)));
3721
+ }
3722
+ justSaved(text) {
3723
+ if (!this.app.readsReports?.()) return of(null);
3724
+ return this.http.get(this.url).pipe(map((reports) => findJustSaved(reports, text) ?? null), catchError(() => of(null)));
3725
+ }
3726
+ undo(id) {
3727
+ return this.http.delete(`${this.url}/${encodeURIComponent(id)}`);
3728
+ }
3729
+ static ɵfac = i0.ɵɵngDeclareFactory({
3730
+ minVersion: "12.0.0",
3731
+ version: "22.2.4",
3732
+ ngImport: i0,
3733
+ type: ReportClient,
3734
+ deps: [],
3735
+ target: i0.ɵɵFactoryTarget.Injectable
3736
+ });
3737
+ static ɵprov = i0.ɵɵngDeclareInjectable({
3738
+ minVersion: "12.0.0",
3739
+ version: "22.2.4",
3740
+ ngImport: i0,
3741
+ type: ReportClient,
3742
+ providedIn: "root"
3743
+ });
3744
+ };
3745
+ i0.ɵɵngDeclareClassMetadata({
3746
+ minVersion: "12.0.0",
3747
+ version: "22.2.4",
3748
+ ngImport: i0,
3749
+ type: ReportClient,
3750
+ decorators: [{
3751
+ type: Injectable,
3752
+ args: [{ providedIn: "root" }]
3753
+ }]
3754
+ });
3755
+ 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_LIST_ROW, SQUARE_MAX_SIDE, 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, startsWheelGesture, stillStep, technicalLines, tellLevel, turningFront, turningRows, viewLevel, whereLevel, wouldLoop };
3400
3756
 
3401
3757
  //# sourceMappingURL=selimssevgi-cube-design-angular.mjs.map