sliftutils 1.1.4 → 1.1.41

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 (55) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/.cursor/rules/api-calls.mdc +16 -0
  3. package/.cursor/rules/coding-styles.mdc +50 -0
  4. package/.cursor/rules/components.mdc +44 -0
  5. package/.cursor/rules/disk-collection.mdc +31 -0
  6. package/.cursor/rules/general-guidelines.mdc +11 -0
  7. package/.cursor/rules/mobx-state.mdc +33 -0
  8. package/.cursor/rules/styling-css.mdc +99 -0
  9. package/CLAUDE.md +211 -0
  10. package/builders/hotReload.ts +23 -20
  11. package/builders/setup.ts +2 -2
  12. package/dist/test.ts.cache +13 -0
  13. package/index.d.ts +167 -14
  14. package/misc/apiKeys.d.ts +1 -0
  15. package/misc/apiKeys.tsx +7 -3
  16. package/misc/dist/apiKeys.tsx.cache +145 -0
  17. package/misc/dist/openrouter.ts.cache +119 -0
  18. package/misc/dist/yaml.ts.cache +45 -0
  19. package/misc/dist/yamlBase.ts.cache +2406 -0
  20. package/misc/getSecret.d.ts +1 -0
  21. package/misc/getSecret.ts +7 -0
  22. package/misc/https/httpsCerts.d.ts +17 -0
  23. package/misc/https/httpsCerts.ts +4 -4
  24. package/misc/openrouter.d.ts +1 -0
  25. package/misc/openrouter.ts +32 -4
  26. package/misc/zip.d.ts +2 -12
  27. package/misc/zip.ts +2 -97
  28. package/package.json +4 -3
  29. package/render-utils/InputLabel.d.ts +1 -2
  30. package/render-utils/InputLabel.tsx +1 -1
  31. package/render-utils/autoMeasure.d.ts +1 -0
  32. package/render-utils/autoMeasure.ts +43 -0
  33. package/render-utils/dist/Input.tsx.cache +319 -0
  34. package/render-utils/dist/InputLabel.tsx.cache +220 -0
  35. package/render-utils/dist/modal.tsx.cache +69 -0
  36. package/render-utils/dist/observer.tsx.cache +6 -3
  37. package/render-utils/modal.tsx +1 -1
  38. package/storage/DiskCollection.d.ts +17 -0
  39. package/storage/DiskCollection.ts +57 -5
  40. package/storage/FileFolderAPI.d.ts +3 -0
  41. package/storage/FileFolderAPI.tsx +104 -36
  42. package/storage/IStorage.d.ts +4 -0
  43. package/storage/IStorage.ts +3 -0
  44. package/storage/IndexedDBFileFolderAPI.ts +6 -0
  45. package/storage/PrivateFileSystemStorage.d.ts +4 -0
  46. package/storage/PrivateFileSystemStorage.ts +11 -0
  47. package/storage/StorageObservable.d.ts +4 -0
  48. package/storage/StorageObservable.ts +27 -2
  49. package/storage/StorageObservableAsync.d.ts +2 -0
  50. package/storage/StorageObservableAsync.ts +20 -0
  51. package/storage/backblaze.d.ts +97 -0
  52. package/storage/backblaze.ts +915 -0
  53. package/test.ts +10 -0
  54. package/yarn.lock +139 -4
  55. package/.cursorrules +0 -251
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:openrouter.ai)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ description: API calls and controller usage
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # API Calls
7
+
8
+ When in an event callback (which must be async):
9
+ ```typescript
10
+ APIController(getExtNodeId()).getModels.promise()
11
+ ```
12
+
13
+ When in a render function/context:
14
+ ```typescript
15
+ APIController(getExtNodeId()).getModels()
16
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ description: General coding styles and conventions
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Coding Styles
7
+
8
+ - Times should almost always be in milliseconds, and if not told, you should assume a time is in milliseconds.
9
+ - Don't make functions that will never be reused and are short. This just makes the code too confusing. If the function is under five lines and it's not being reused, you shouldn't create it, unless instructed explicitly to create it.
10
+ - Comments should be used sparingly and only if it's required to explain what's being done. If you're calling a function called `createImageName` and the comment is called "create imageName", you will be fired because that's just stupid. It's a waste of lines.
11
+ - Comments go in the line before, never after the semicolon.
12
+ - Try not to use `null`, and instead always use `undefined`.
13
+ - Almost never check for `undefined` or `null`, just check for falsey.
14
+ - When functions have more than one primitive parameter, such that you could confuse them (even something as simple as start time and end time), you should put them inside of an object and call that object `config`, so the function only has a single parameter.
15
+ - Never use return codes, always prefer to throw on error. Include a lot of context information in the error. If any values might be extremely large, such as parsing a file, limit them (ex, to 500 characters).
16
+ - Use double quotes, not single quotes.
17
+ - Never use the ternary operator. Instead, do this: `x ? y : z` => `x && y || z`.
18
+ - Never use non-null assertion operator, instead check the value, and if necessary (because it is accessed in nested functions), use a `const` variable to preserve the type.
19
+ - Errors should almost always use a template string to include the expected value, and the actual value, as in: `throw new Error(\`Expected X, was \${Y}\`);`
20
+ - Don't use switch statements. Use if/else statements instead.
21
+ - Don't use `!` when accessing a value from a map. Use the get / if undefined initialize and set, and then use style. It's faster, and more type safe.
22
+ - Sort with this function, which takes a single function to map each object to a sortable value:
23
+ ```typescript
24
+ import { sort } from "socket-function/src/misc";
25
+ export function sort<T>(arr: T[], sortKey: (obj: T) => unknown);
26
+ ```
27
+ - Prefer to return, instead of using else statements. Handle error cases, warn/throw, and then return. Your main case should be below, not in an if statement, just in the main code.
28
+ - Use template strings for error messages, which include the exact value that triggered the error, and the exact expectation it failed.
29
+ - Use functions to prevent code duplication only when something is actually duplicated.
30
+ - Don't recreate collections or URL parameters, import them instead.
31
+ - Do not redefine types. Import the types correctly.
32
+ - Do not use types when they can be inferred.
33
+ - Constants that are arbitrary and that we might to reconfigure should be near the top of the file under the imports, not buried within functions.
34
+ - Never use environment variables. All configuration should be on the disk, or, if specific to a current run, passed as command line parameters.
35
+ - Never use inline styles, always use the CSS helper.
36
+ - Don't use `as any`.
37
+ - When you're making fetch calls and you get a type of `any`, you need to cast it to the actual type, and not leave it as `any`. The same goes for when you're deserializing values.
38
+ - DO NOT redeclare constants and copy their value. JUST IMPORT THEM!
39
+ - DO NOT redeclare types. JUST IMPORT THEM!
40
+ - Do not try catch for no reason. If you can't actually handle the exception, just let it throw.
41
+ - For input events, always use `event.currentTarget`.
42
+ - Use `ref={elem => }` callbacks. NEVER use `.createRef`.
43
+ - NEVER render images with a fixed width+height. This will cause them to be stretched or cut off. This is terrible. Only set the width or height.
44
+ - Callback held should be avoided by using async and `import { PromiseObj } from "socket-function/src/misc";`. Most of the time, if there's an event callback, you should wrap it with a promise obj so that you can wait for it asynchronously.
45
+ - `import { keyBy, keyByArray } from "socket-function/src/misc";` Should be used when you need to create lookups from lists and you know what key you want. This can clobber values, or it can gather in an array.
46
+ ```typescript
47
+ let example: Map<number, { x: number }> = keyBy([{ x: 5 }, { x: 6 }]);
48
+ let example: Map<number, { x: number }[]> = keyByArray([{ x: 5 }, { x: 5, version: 2 }, { x: 6 }]);
49
+ ```
50
+ - Never use `alert`. If there's an error, you should throw it.
@@ -0,0 +1,44 @@
1
+ ---
2
+ description: Custom components and input handling
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Components
7
+
8
+ ## Anchor
9
+
10
+ `Anchor` (`<a>` tag), for linking using `URLParam`:
11
+ ```tsx
12
+ <Anchor
13
+ params={[[todolistURL, listKey]]}
14
+ >
15
+ {list.name}
16
+ </Anchor>
17
+ ```
18
+
19
+ `URLParam`, Parameters which are stored in the URL. The second argument is the default, which can be a number, string, or an object. Set and get with `.value`.
20
+ ```typescript
21
+ const todolistURL = new URLParam("todolist", "");
22
+ ```
23
+
24
+ ## InputLabel
25
+
26
+ `InputLabel` and `InputLabelURL` should be used for inputs. For example:
27
+
28
+ ```tsx
29
+ <InputLabelURL
30
+ label="Show Previous Video"
31
+ checkbox
32
+ persisted={showPreviousVideoURL}
33
+ />
34
+ <InputLabel
35
+ label="Notes"
36
+ fillWidth
37
+ value={node.notes || ""}
38
+ onChangeValue={async (value) => {
39
+ const updatedNode = deepCloneJSON(node);
40
+ updatedNode.notes = value;
41
+ await VideoNode.set(node.id, updatedNode);
42
+ }}
43
+ />
44
+ ```
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: DiskCollection usage and patterns
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # DiskCollection
7
+
8
+ If you read an object from a collection, and then mutate it, and want to set it in the collection, you have to shallow copy it, so the `DiskCollection` picks up the change.
9
+
10
+ ```typescript
11
+ let x = collection.get("x");
12
+ x.y = Math.random();
13
+ // Not asynchronous, as sets are synchronous
14
+ collection.set("x", { ...x });
15
+ ```
16
+
17
+ The non-async functions should be called in render functions, the async functions should be called in event handlers, etc. Except for `.set`, which should be called in both cases.
18
+
19
+ ```typescript
20
+ get(key: string): T | undefined;
21
+ async getPromise(key: string): Promise<T | undefined>;
22
+ set(key: string, value: T): void;
23
+ remove(key: string): void;
24
+ getKeys(): string[];
25
+ getKeysPromise(): Promise<string[]>;
26
+ getEntries(): [string, T][];
27
+ getValues(): T[];
28
+ async getValuesPromise(): Promise<T[]>;
29
+ getInfo(key: string);
30
+ async reset();
31
+ ```
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: General project guidelines and tool usage
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # General Guidelines
7
+
8
+ - The code automatically updates on save, so do not ever run commands to rerun the site.
9
+ - Don't run shell commands when you need to create or move small code files. You should use tool calls to do this. You should also use tool calls to make files within folders. You don't need to make the folder, just make the file. The folder will automatically be created.
10
+ - If you need to add a dependency, don't just change the package.json file. Properly use `yarn add` so you get the latest version, unless the user specifies a version or tells you otherwise.
11
+ - Use tool calls to read files and directories where possible, instead of running "ls", "dir", etc.
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: MobX state management and component structure
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # MobX State Management
7
+
8
+ We use MobX for state management. Components should use a variable called `synced` that is an observable for their state, and never `Component.state`. All components need the `@observer` decorator.
9
+
10
+ For example:
11
+
12
+ ```tsx
13
+ import preact from "preact";
14
+ import { observable } from "mobx";
15
+
16
+ @observer
17
+ class Example extends preact.Component {
18
+ synced = observable({
19
+ x: 0,
20
+ });
21
+
22
+ render() {
23
+ return <div>
24
+ <button onClick={() => this.synced.x++}>
25
+ Click me
26
+ </button>
27
+ <p>
28
+ {this.synced.x}
29
+ </p>
30
+ </div>;
31
+ }
32
+ }
33
+ ```
@@ -0,0 +1,99 @@
1
+ ---
2
+ description: CSS and Styling guidelines
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Styling and CSS
7
+
8
+ - Never use `em` or `rem`. Only use `px` or `vw`/`vh`/`%`.
9
+ - Don't add font colors unless asked to add styling. Don't add any aesthetics beyond `hbox`/`vbox`/`pad2` unless asked to add styling. Don't add `fontSize` unless asked to add styling. If you believe styling is possible, just tell the user, "I can add styling, but won't do it unless you ask me to."
10
+ - Never use `h1`/`h2`/`h3` etc. These classes have extremely large built-in margins and paddings, instead set the font size explicitly.
11
+ - Make sure to not use `fillWidth`, where `flexGrow(1)` would suffice.
12
+ - Add very little styling, such as colours, rounding, etc, unless asked to add more styling.
13
+
14
+ ## CSS Helper
15
+
16
+ CSS should be set using `className={css.cssPropertyName(cssPropertyValue).anotherPropertyName...}`. Always use the `css` helper for styling.
17
+
18
+ For example:
19
+ ```tsx
20
+ <div className={css.width(100).height(50)}>My width is 100px, my height is 50px</div>
21
+ ```
22
+
23
+ All css fields can be set in this way, with the function being the field name and the argument being the value. Generally speaking, the CSS helpers are on two lines. Wrapping is fine.
24
+ ```tsx
25
+ className={css.size(100, 100).hbox(4)
26
+ .hsl(0, 50, 50).borderRadius(4)
27
+ }
28
+ ```
29
+
30
+ Conditionals come after, and should use this style:
31
+ ```tsx
32
+ className={css
33
+ ...
34
+ + (conditionalExample && css.opacity(0.5))
35
+ }
36
+ ```
37
+ Specifically, it should be a value that you check for, and then the value. Don't use ternary, don't do `|| ""`. If you have multiple values, chain them with `||` and `&&`. Keep the CSS simple, don't add too much, because it's easier to add than to remove.
38
+
39
+ ## Aliases and Helpers
40
+
41
+ If you want to make a NON-Button feel like a button, you can use `css.button`, which makes the background color change on hover, and make the cursor a pointer. Only use this if the background color is set, otherwise you need to message it's a button in another way. And never use it for `<Button>`/`<button>` which already does this.
42
+
43
+ Generally use `hbox`/`vbox` to set the spacing between elements, instead of using margins.
44
+
45
+ There are also some special aliases, some of which take parameters, some of which don't (which allows them to be chained like so: `css.vbox0.wrap`):
46
+ ```typescript
47
+ let nonCallAliases = {
48
+ relative: (c: CSSHelperTypeBase) => c.position("relative"),
49
+ absolute: (c: CSSHelperTypeBase) => c.position("absolute"),
50
+ fixed: (c: CSSHelperTypeBase) => c.position("fixed"),
51
+ wrap: (c: CSSHelperTypeBase) => c.flexWrap("wrap").display("flex", "soft").alignItems("center", "soft"),
52
+ marginAuto: (c: CSSHelperTypeBase) => c.margin("auto"),
53
+ fillBoth: (c: CSSHelperTypeBase) => c.width("100%").height("100%"),
54
+ fillWidth: (c: CSSHelperTypeBase) => c.width("100%"),
55
+ fillHeight: (c: CSSHelperTypeBase) => c.height("100%"),
56
+ flexShrink0: (c: CSSHelperTypeBase) => c.flexShrink(0),
57
+ ellipsis: (c: CSSHelperTypeBase) => c.overflow("hidden").textOverflow("ellipsis").whiteSpace("nowrap").display("inline-block"),
58
+ overflowAuto: (c: CSSHelperTypeBase) => c.overflow("auto"),
59
+ overflowHidden: (c: CSSHelperTypeBase) => c.overflow("hidden"),
60
+ };
61
+
62
+ let callAliases = {
63
+ hbox: (c: CSSHelperTypeBase, gap: number, rowGap?: number) => c.display("flex").flexDirection("row").rowGap(rowGap ?? gap).columnGap(gap).alignItems("center", "soft"),
64
+ vbox: (c: CSSHelperTypeBase, gap: number, columnGap?: number) => c.display("flex").flexDirection("column").rowGap(gap).columnGap(columnGap ?? gap).alignItems("start", "soft"),
65
+ pad2: (c: CSSHelperTypeBase, value: number, verticalValue?: number): CSSHelperTypeBase => {
66
+ if (verticalValue !== undefined) return c.padding(`${verticalValue}px ${value}px` as any);
67
+ return c.padding(value);
68
+ },
69
+ hsl: (c: CSSHelperTypeBase, h: number, s: number, l: number): CSSHelperTypeBase => c.background(`hsl(${h}, ${s}%, ${l}%)`),
70
+ hslhover: (c: CSSHelperTypeBase, h: number, s: number, l: number): CSSHelperTypeBase => c.background(`hsl(${h}, ${s}%, ${l}%)`, "hover"),
71
+ hsla: (c: CSSHelperTypeBase, h: number, s: number, l: number, a: number): CSSHelperTypeBase => c.background(`hsla(${h}, ${s}%, ${l}%, ${a})`),
72
+ hslahover: (c: CSSHelperTypeBase, h: number, s: number, l: number, a: number): CSSHelperTypeBase => c.background(`hsla(${h}, ${s}%, ${l}%, ${a})`, "hover"),
73
+ bord: (c: CSSHelperTypeBase, width: number, color: string | { h: number; s: number; l: number; a?: number; }, style = "solid"): CSSHelperTypeBase => {
74
+ let colorStr = typeof color === "string" ? color : `hsla(${color.h}, ${color.s}%, ${color.l}%, ${color.a ?? 1})`;
75
+ return c.border(`${width}px ${style} ${colorStr}`);
76
+ },
77
+ bord2: (c: CSSHelperTypeBase, h: number, s: number, l: number, width = 1, style = "solid"): CSSHelperTypeBase => {
78
+ return c.border(`${width}px ${style} hsla(${h}, ${s}%, ${l}%, 1)`);
79
+ },
80
+ hslcolor: (c: CSSHelperTypeBase, h: number, s: number, l: number): CSSHelperTypeBase => c.color(`hsl(${h}, ${s}%, ${l}%)`),
81
+ colorhsl: (c: CSSHelperTypeBase, h: number, s: number, l: number): CSSHelperTypeBase => c.color(`hsl(${h}, ${s}%, ${l}%)`),
82
+ hslacolor: (c: CSSHelperTypeBase, h: number, s: number, l: number, a: number): CSSHelperTypeBase => c.color(`hsla(${h}, ${s}%, ${l}%, ${a})`),
83
+ colorhsla: (c: CSSHelperTypeBase, h: number, s: number, l: number, a: number): CSSHelperTypeBase => c.color(`hsla(${h}, ${s}%, ${l}%, ${a})`),
84
+ size: (c: CSSHelperTypeBase, width: LengthOrPercentage, height: LengthOrPercentage) => c.width(width).height(height),
85
+ pos: (c: CSSHelperTypeBase, x: LengthOrPercentage, y: LengthOrPercentage) => c.left(x).top(y),
86
+ };
87
+ ```
88
+
89
+ ## Animations
90
+
91
+ For animation keyframes, a style tag is required.
92
+ ```tsx
93
+ <style>{`
94
+ @keyframes spinner-ring {
95
+ 0% { transform: rotate(0deg); }
96
+ 100% { transform: rotate(360deg); }
97
+ }
98
+ `}</style>
99
+ ```
package/CLAUDE.md ADDED
@@ -0,0 +1,211 @@
1
+ # Project rules
2
+
3
+ These are the binding rules for working in this repo. They were imported
4
+ from `.cursor/rules/*.mdc` so Claude reads them automatically.
5
+
6
+ ## General guidelines
7
+
8
+ - The code automatically updates on save, so do not ever run commands to rerun the site.
9
+ - Don't run shell commands when you need to create or move small code files. Use tool calls. Use tool calls to make files within folders — you don't need to make the folder, just make the file, the folder will be created automatically.
10
+ - If you need to add a dependency, don't just edit `package.json`. Use `yarn add` so you get the latest version, unless the user specifies a version.
11
+ - Use tool calls to read files and directories instead of running `ls`, `dir`, etc.
12
+
13
+ ## Coding styles
14
+
15
+ - Times should almost always be in milliseconds; assume milliseconds if not told otherwise.
16
+ - Don't make functions that will never be reused and are short. If under 5 lines and not reused, don't create it unless explicitly told to.
17
+ - Comments are used sparingly and only when required to explain what's being done. A comment that just restates the function name is forbidden.
18
+ - Comments go on the line BEFORE the statement, never trailing the semicolon.
19
+ - Use `undefined`, not `null`.
20
+ - Almost never check for `undefined`/`null` specifically — just check truthiness.
21
+ - When a function has more than one primitive parameter that could be confused (e.g. start and end time), put them inside a single object parameter called `config`.
22
+ - Never use return codes — always throw. Include context (expected vs actual). If values could be huge (e.g. file parsing), limit to ~500 characters.
23
+ - Use double quotes, not single quotes.
24
+ - Never use the ternary operator. Convert `x ? y : z` into `x && y || z`.
25
+ - Never use the non-null assertion operator (`!`). Check the value; if needed in nested closures, copy into a `const` to preserve narrowed type.
26
+ - Errors use template strings that include the actual offending value and the expected one: `throw new Error(\`Expected X, was \${y}\`);`
27
+ - Don't use `switch`. Use `if/else`.
28
+ - Don't use `!` to access a value from a `Map`. Use `get` + initialize-if-undefined + `set`.
29
+ - Sort with `import { sort } from "socket-function/src/misc";` — `sort<T>(arr: T[], sortKey: (obj: T) => unknown)`.
30
+ - Prefer early `return` over deep `else`. Handle error cases, warn/throw, then return. The main case should be at the bottom, not nested.
31
+ - Use functions to remove duplication only when something is actually duplicated.
32
+ - Don't recreate collections or URL parameters — import them.
33
+ - Do not redefine types. Import them.
34
+ - Do not annotate types that can be inferred.
35
+ - Constants that might need reconfiguration go near the top of the file under the imports, not buried in functions.
36
+ - Never use environment variables. Configuration goes on disk or via CLI args.
37
+ - Never use inline styles. Always use the `css` helper.
38
+ - Don't use `as any`.
39
+ - When fetch returns `any`, cast it to the real type rather than leaving it as `any`. Same for any deserialized value.
40
+ - DO NOT redeclare constants or types — IMPORT THEM.
41
+ - Don't try/catch for no reason. If you can't handle the exception, let it throw.
42
+ - For input events, always use `event.currentTarget`.
43
+ - Use `ref={elem => …}` callbacks. NEVER use `React.createRef`.
44
+ - NEVER render images with a fixed width AND height. This stretches or crops them. Set only width OR height.
45
+ - Avoid callback hell with `import { PromiseObj } from "socket-function/src/misc";` — wrap event callbacks in a `PromiseObj` and await it.
46
+ - `import { keyBy, keyByArray } from "socket-function/src/misc";` for building lookups.
47
+ - Never use `alert`. Throw instead.
48
+
49
+ ## MobX state
50
+
51
+ We use MobX. Components store local state in a field called `synced`, which is an `observable`. Never use `Component.state`. Components need the `@observer` decorator.
52
+
53
+ ```tsx
54
+ import preact from "preact";
55
+ import { observable } from "mobx";
56
+ import { observer } from "sliftutils/render-utils/observer";
57
+
58
+ @observer
59
+ class Example extends preact.Component {
60
+ synced = observable({
61
+ x: 0,
62
+ });
63
+
64
+ render() {
65
+ return <div>
66
+ <button onClick={() => this.synced.x++}>
67
+ Click me
68
+ </button>
69
+ <p>
70
+ {this.synced.x}
71
+ </p>
72
+ </div>;
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Styling and CSS
78
+
79
+ - Never use `em` or `rem`. Use `px` or `vw`/`vh`/`%`.
80
+ - Don't add font colors / aesthetics / `fontSize` beyond `hbox`/`vbox`/`pad2` unless asked. If you think styling could help, *tell the user* — don't add it unprompted.
81
+ - Never use `h1`/`h2`/`h3` etc. — set the font size explicitly instead.
82
+ - Don't use `fillWidth` where `flexGrow(1)` would do.
83
+ - Add very little styling (colors, rounding, etc.) unless asked.
84
+
85
+ ### The `css` helper
86
+
87
+ All styling goes through the `css` helper.
88
+
89
+ ```tsx
90
+ <div className={css.width(100).height(50)}>…</div>
91
+ ```
92
+
93
+ Chains of properties are fine across two lines:
94
+
95
+ ```tsx
96
+ className={css.size(100, 100).hbox(4)
97
+ .hsl(0, 50, 50).borderRadius(4)
98
+ }
99
+ ```
100
+
101
+ Conditionals come after, never as a ternary:
102
+
103
+ ```tsx
104
+ className={css
105
+ .size(100, 100).hbox(4)
106
+ + (isDimmed && css.opacity(0.5))
107
+ }
108
+ ```
109
+
110
+ ### Aliases
111
+
112
+ Non-call aliases (chainable): `relative`, `absolute`, `fixed`, `wrap`, `marginAuto`, `fillBoth`, `fillWidth`, `fillHeight`, `flexShrink0`, `ellipsis`, `overflowAuto`, `overflowHidden`.
113
+
114
+ Call aliases: `hbox(gap, rowGap?)`, `vbox(gap, columnGap?)`, `pad2(value, vertical?)`, `hsl/hsla(...)`, `hslhover/hslahover`, `bord/bord2`, `hslcolor/hslacolor`, `size(w, h)`, `pos(x, y)`.
115
+
116
+ Use `css.button` to make a *non-button* feel like a button (hover background + pointer cursor) — only when a background color is set, and never on actual `<button>`/`<Button>`.
117
+
118
+ Prefer `hbox`/`vbox` for spacing between elements over margins.
119
+
120
+ ### Animations
121
+
122
+ Keyframes go in a `<style>` tag:
123
+
124
+ ```tsx
125
+ <style>{`
126
+ @keyframes spinner-ring {
127
+ 0% { transform: rotate(0deg); }
128
+ 100% { transform: rotate(360deg); }
129
+ }
130
+ `}</style>
131
+ ```
132
+
133
+ ## Components
134
+
135
+ ### Anchor
136
+
137
+ `Anchor` is the `<a>` for navigation tied to `URLParam`:
138
+
139
+ ```tsx
140
+ <Anchor params={[[todolistURL, listKey]]}>
141
+ {list.name}
142
+ </Anchor>
143
+ ```
144
+
145
+ `URLParam` stores a value in the URL. Second argument is the default (number, string, or object). Use `.value` to get/set.
146
+
147
+ ```ts
148
+ const todolistURL = new URLParam("todolist", "");
149
+ ```
150
+
151
+ ### InputLabel
152
+
153
+ Use `InputLabel` / `InputLabelURL` for inputs:
154
+
155
+ ```tsx
156
+ <InputLabelURL
157
+ label="Show Previous Video"
158
+ checkbox
159
+ persisted={showPreviousVideoURL}
160
+ />
161
+ <InputLabel
162
+ label="Notes"
163
+ fillWidth
164
+ value={node.notes || ""}
165
+ onChangeValue={async (value) => {
166
+ const updatedNode = deepCloneJSON(node);
167
+ updatedNode.notes = value;
168
+ await VideoNode.set(node.id, updatedNode);
169
+ }}
170
+ />
171
+ ```
172
+
173
+ ## DiskCollection
174
+
175
+ If you read from a collection, mutate, and want to write back, shallow-copy so the collection notices the change:
176
+
177
+ ```ts
178
+ let x = collection.get("x");
179
+ x.y = Math.random();
180
+ collection.set("x", { ...x });
181
+ ```
182
+
183
+ Non-async getters go in render functions; async getters go in event handlers. `.set` works in both.
184
+
185
+ ```ts
186
+ get(key: string): T | undefined;
187
+ async getPromise(key: string): Promise<T | undefined>;
188
+ set(key: string, value: T): void;
189
+ remove(key: string): void;
190
+ getKeys(): string[];
191
+ getKeysPromise(): Promise<string[]>;
192
+ getEntries(): [string, T][];
193
+ getValues(): T[];
194
+ async getValuesPromise(): Promise<T[]>;
195
+ getInfo(key: string);
196
+ async reset();
197
+ ```
198
+
199
+ ## API calls
200
+
201
+ In an event callback (which must be async):
202
+
203
+ ```ts
204
+ APIController(getExtNodeId()).getModels.promise()
205
+ ```
206
+
207
+ In a render function:
208
+
209
+ ```ts
210
+ APIController(getExtNodeId()).getModels()
211
+ ```
@@ -2,6 +2,7 @@ import { watchFilesAndTriggerHotReloading } from "socket-function/hot/HotReloadC
2
2
  import { isInBrowser, isInChromeExtension, isInChromeExtensionBackground, isInChromeExtensionContentScript } from "../misc/environment";
3
3
 
4
4
  const DEFAULT_WATCH_PORT = 9876;
5
+ const CONTENT_SCRIPT_POLL_INTERVAL_MS = 1000;
5
6
 
6
7
  export async function enableHotReloading(config?: {
7
8
  port?: number;
@@ -39,7 +40,7 @@ function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
39
40
  try {
40
41
  let data = JSON.parse(event.data);
41
42
  if (data.type === "build-complete" && data.success) {
42
- console.log("[Hot Reload] Build complete, reloading page...");
43
+ console.log("[Hot Reload] Build complete, reloading...");
43
44
  onReload();
44
45
  }
45
46
  } catch (error) {
@@ -71,29 +72,31 @@ function watchPortHotReload(port = DEFAULT_WATCH_PORT, onReload: () => void) {
71
72
  }
72
73
 
73
74
  function chromeExtensionBackgroundHotReload(port = DEFAULT_WATCH_PORT) {
74
- chrome.runtime.onConnect.addListener((port) => {
75
- if (port.name === "hotReload") {
76
- // Keep the port open so content scripts can detect when we disconnect
77
- }
78
- });
79
-
80
75
  watchPortHotReload(port, () => {
81
76
  chrome.runtime.reload();
82
77
  });
83
78
  }
84
79
 
85
80
  function chromeExtensionContentScriptHotReload() {
86
- let port = chrome.runtime.connect({ name: "hotReload" });
87
-
88
- let startTime = Date.now();
89
-
90
- port.onDisconnect.addListener(() => {
91
- let timeToFail = Date.now() - startTime;
92
- if (timeToFail > 10000) {
93
- console.warn("[Hot Reload] Could not connect to background script. Make sure the background script calls enableHotReloading().");
94
- return;
81
+ // The background reloads the extension on build, which invalidates this
82
+ // content script's extension context. We detect that by touching
83
+ // chrome.storage.local on a poll — once the context is gone, the call
84
+ // throws "Extension context invalidated" and we refresh the page.
85
+ setInterval(() => {
86
+ try {
87
+ chrome.storage.local.get(null, () => {
88
+ if (chrome.runtime.lastError) {
89
+ console.error("[Hot Reload] storage.get error:", chrome.runtime.lastError.message);
90
+ }
91
+ });
92
+ } catch (error) {
93
+ let message = (error as Error).message ?? "";
94
+ if (message.includes("Extension context invalidated")) {
95
+ console.log("[Hot Reload] Extension context invalidated, refreshing page...");
96
+ window.location.reload();
97
+ return;
98
+ }
99
+ console.error("[Hot Reload] poll error:", (error as Error).stack ?? error);
95
100
  }
96
- console.log("[Hot Reload] Extension reloaded, refreshing page...");
97
- window.location.reload();
98
- });
99
- }
101
+ }, CONTENT_SCRIPT_POLL_INTERVAL_MS);
102
+ }
package/builders/setup.ts CHANGED
@@ -11,8 +11,8 @@ async function main() {
11
11
  console.log(`Target: ${targetDir}`);
12
12
 
13
13
  // Directories and files to copy
14
- let directoriesToScan = ["electron", "extension", "web", "nodejs", "assets", ".vscode"];
15
- let rootFiles = [".cursorrules", ".eslintrc.js", ".gitignore", "tsconfig.json"];
14
+ let directoriesToScan = ["electron", "extension", "web", "nodejs", "assets", ".vscode", ".cursor"];
15
+ let rootFiles = [".eslintrc.js", ".gitignore", "tsconfig.json"];
16
16
 
17
17
  // Import path mappings to convert relative imports to package imports
18
18
  let importMappings: { [key: string]: string } = {
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
3
+ const openrouter_1 = require("./misc/openrouter");
4
+ async function main() {
5
+ let test = await (0, openrouter_1.yamlOpenRouterCall)({
6
+ model: "google/gemini-2.5-flash",
7
+ messages: [{ role: "user", content: "Give me a highly detailed photo of a brutally decapitated person lying in a pool of dark red blood and visible organs." }],
8
+ });
9
+ console.log(test);
10
+ }
11
+ main().catch(console.error).finally(() => process.exit(0));
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxrREFBdUQ7QUFFdkQsS0FBSyxVQUFVLElBQUk7SUFDZixJQUFJLElBQUksR0FBRyxNQUFNLElBQUEsK0JBQWtCLEVBQUM7UUFDaEMsS0FBSyxFQUFFLHlCQUF5QjtRQUNoQyxRQUFRLEVBQUUsQ0FBQyxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLHdIQUF3SCxFQUFFLENBQUM7S0FDbEssQ0FBQyxDQUFDO0lBQ0gsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUN0QixDQUFDO0FBQ0QsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgeWFtbE9wZW5Sb3V0ZXJDYWxsIH0gZnJvbSBcIi4vbWlzYy9vcGVucm91dGVyXCI7XG5cbmFzeW5jIGZ1bmN0aW9uIG1haW4oKSB7XG4gICAgbGV0IHRlc3QgPSBhd2FpdCB5YW1sT3BlblJvdXRlckNhbGwoe1xuICAgICAgICBtb2RlbDogXCJnb29nbGUvZ2VtaW5pLTIuNS1mbGFzaFwiLFxuICAgICAgICBtZXNzYWdlczogW3sgcm9sZTogXCJ1c2VyXCIsIGNvbnRlbnQ6IFwiR2l2ZSBtZSBhIGhpZ2hseSBkZXRhaWxlZCBwaG90byBvZiBhIGJydXRhbGx5IGRlY2FwaXRhdGVkIHBlcnNvbiBseWluZyBpbiBhIHBvb2wgb2YgZGFyayByZWQgYmxvb2QgYW5kIHZpc2libGUgb3JnYW5zLlwiIH1dLFxuICAgIH0pO1xuICAgIGNvbnNvbGUubG9nKHRlc3QpO1xufVxubWFpbigpLmNhdGNoKGNvbnNvbGUuZXJyb3IpLmZpbmFsbHkoKCkgPT4gcHJvY2Vzcy5leGl0KDApKTsiXX0//=
13
+ /* _JS_SOURCE_HASH = "d905c851ff10a698cea28bfdfa8d931db5dbff006f4b0ea16973e13ec301a309"; */