sliftutils 1.1.4 → 1.1.40
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/.claude/settings.local.json +7 -0
- package/.cursor/rules/api-calls.mdc +16 -0
- package/.cursor/rules/coding-styles.mdc +50 -0
- package/.cursor/rules/components.mdc +44 -0
- package/.cursor/rules/disk-collection.mdc +31 -0
- package/.cursor/rules/general-guidelines.mdc +11 -0
- package/.cursor/rules/mobx-state.mdc +33 -0
- package/.cursor/rules/styling-css.mdc +99 -0
- package/CLAUDE.md +211 -0
- package/builders/hotReload.ts +23 -20
- package/builders/setup.ts +2 -2
- package/dist/test.ts.cache +13 -0
- package/index.d.ts +166 -14
- package/misc/apiKeys.d.ts +1 -0
- package/misc/apiKeys.tsx +7 -3
- package/misc/dist/apiKeys.tsx.cache +145 -0
- package/misc/dist/openrouter.ts.cache +119 -0
- package/misc/dist/yaml.ts.cache +45 -0
- package/misc/dist/yamlBase.ts.cache +2406 -0
- package/misc/https/httpsCerts.d.ts +17 -0
- package/misc/https/httpsCerts.ts +4 -4
- package/misc/openrouter.d.ts +1 -0
- package/misc/openrouter.ts +32 -4
- package/misc/zip.d.ts +2 -12
- package/misc/zip.ts +2 -97
- package/package.json +4 -3
- package/render-utils/InputLabel.d.ts +1 -2
- package/render-utils/InputLabel.tsx +1 -1
- package/render-utils/autoMeasure.d.ts +1 -0
- package/render-utils/autoMeasure.ts +43 -0
- package/render-utils/dist/Input.tsx.cache +319 -0
- package/render-utils/dist/InputLabel.tsx.cache +220 -0
- package/render-utils/dist/modal.tsx.cache +69 -0
- package/render-utils/dist/observer.tsx.cache +6 -3
- package/render-utils/modal.tsx +1 -1
- package/storage/DiskCollection.d.ts +17 -0
- package/storage/DiskCollection.ts +57 -5
- package/storage/FileFolderAPI.d.ts +3 -0
- package/storage/FileFolderAPI.tsx +104 -36
- package/storage/IStorage.d.ts +4 -0
- package/storage/IStorage.ts +3 -0
- package/storage/IndexedDBFileFolderAPI.ts +6 -0
- package/storage/PrivateFileSystemStorage.d.ts +4 -0
- package/storage/PrivateFileSystemStorage.ts +11 -0
- package/storage/StorageObservable.d.ts +4 -0
- package/storage/StorageObservable.ts +27 -2
- package/storage/StorageObservableAsync.d.ts +2 -0
- package/storage/StorageObservableAsync.ts +20 -0
- package/storage/backblaze.d.ts +97 -0
- package/storage/backblaze.ts +915 -0
- package/test.ts +10 -0
- package/yarn.lock +139 -4
- package/.cursorrules +0 -251
|
@@ -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
|
+
```
|
package/builders/hotReload.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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 = [".
|
|
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"; */
|