Skip to content

Commit 843a1b6

Browse files
committed
minor improvements in various modules; dev guidelines for bots
* `prax.mjs`: added `Ren..E`, a catch-all rendering function. * `dom.mjs`: rename `clip` -> `copyToClipboard`; now preserves current focus. * `cli.mjs`: `emptty`: fix `Deno.isatty`. * `lang.mjs`: add `round`. * `obs_dom.mjs`: add shortcut `funText`. * `obs.mjs`: the `deinit` function now passes additional arguments, if any, to the target to be deinited. * `http_deno.mjs` and `dom_reg.mjs`: replaced unnecessary collection classes with simple static dicts. * Add `claude.md` with development guidelines for LLMs (and humans). * Various other misc fixes and improvements.
1 parent 3f248e8 commit 843a1b6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+951
-532
lines changed

.gitignore

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
.DS_Store
1+
.*
22
/*
3-
3+
!/.github
44
!/.*rc
55
!/.*ignore
66
!/*.mjs
77
!/readme.md
8+
!/claude.md
89
!/makefile
910
!/package.json
10-
11-
!/.github
1211
!/doc
1312
!/docs
1413
!/test
15-
16-
mock_*
17-
stash_*
18-
*_mock.*
19-
*_stash.*
14+
/prompts.md

claude.md

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
# Library Development Guidelines
2+
3+
This document provides guidelines for developing and maintaining this library. It is very important to accurately follow all these guidelines.
4+
5+
## Workflow Guidelines
6+
7+
### Version Control
8+
9+
* Store every user prompt in `prompts.md` immediately upon receiving it:
10+
```
11+
<previous prompts>
12+
13+
---
14+
15+
<user's prompt verbatim>
16+
```
17+
* Commit changes after completing any task
18+
* When committing changes:
19+
1. Use a concise title that briefly summarizes the changes
20+
2. Include a more detailed summary in the commit body
21+
3. Include all user prompts received since the last commit:
22+
```bash
23+
git commit -am "Brief summary title" -m "$(cat <<'EOF'
24+
Detailed summary of changes.
25+
26+
<prompt 1>
27+
28+
<prompt 2>
29+
30+
<prompt n...>
31+
EOF
32+
)"
33+
```
34+
* Note: `prompts.md` is a local log file and should not be committed to version control
35+
* Each prompt in `prompts.md` should be separated by `---` surrounded by blank lines
36+
* Use a single commit command rather than separate add/commit steps
37+
38+
## Project Overview
39+
40+
`@mitranim/js` is a lightweight JavaScript "standard library" that provides utilities across various domains while maintaining these principles:
41+
42+
* Environment-independent (browsers, Deno, Node)
43+
* No external dependencies
44+
* No transpilation required
45+
* No bundlers required
46+
* Native JS modules only
47+
48+
## Code Organization
49+
50+
The codebase is organized into focused modules:
51+
52+
* `lang.mjs`: Type assertions and core utilities
53+
* `iter.mjs`: Functional iteration utilities
54+
* `obj.mjs`: Object manipulation
55+
* `coll.mjs`: Enhanced data structures
56+
* Plus specialized modules for DOM, HTTP, paths, etc.
57+
58+
## Code Style Guidelines
59+
60+
### Naming Conventions
61+
62+
* `camelCase` for functions and properties
63+
* `PascalCase` for classes
64+
* Type checking functions follow specific patterns:
65+
* `isThing`: Returns boolean
66+
* `reqThing`: Requires value to be of type Thing
67+
* `optThing`: Accepts Thing or null/undefined
68+
* `onlyThing`: Filters out non-Thing values
69+
70+
### Syntax Conventions
71+
72+
* **No semicolons** — rely on JavaScript's Automatic Semicolon Insertion
73+
* Use backticks (\`) for string literals even for single words (except in import statements, which require quotes)
74+
* Use spaces, not tabs (2-space indentation)
75+
* Prefer single-line conditions when simple: `if (condition) return value`
76+
* Use braces for multi-line blocks
77+
* Prefer ternary expressions for simple conditionals: `condition ? valueA : valueB`
78+
* Prefer early returns over nested if/else structures
79+
* Use single-line arrow functions without braces for simple returns
80+
* Multi-line arrow functions use braces and explicit return
81+
* Avoid extra spacing in single-line parentheses, brackets, and braces: `[1, 2]` not `[ 1, 2 ]`
82+
* No spaces between parentheses and function bodies: `method() {return value}` not `method() { return value }`
83+
84+
### Variable Declarations
85+
86+
* Use `const` for variables that aren't reassigned (preferred)
87+
* Use `let` for variables that need reassignment
88+
* Never use `var`
89+
* Avoid parameter default values with complex expressions
90+
* Avoid parameter destructuring
91+
92+
### Naming and Organization
93+
94+
* Group related functions together
95+
* Place internal utility functions at the bottom of files
96+
* Use consistent prefixes (`is/req/opt/only`) for type-related functions
97+
* Use descriptive names, with abbreviations marked with "Short for" comments
98+
* Keep function bodies concise and focused
99+
* Prefer small, simple, focused functions over large ones — each function should do one thing well
100+
101+
### Programming Paradigm
102+
103+
* Functional programming emphasized
104+
* Immutability preferred with explicit mutations when needed
105+
* Minimal dependencies between modules
106+
* Type safety through runtime checks
107+
* Prefer statically defined functions over inline closures:
108+
```javascript
109+
// Preferred: Named function definition
110+
function mapValues(obj) {
111+
return Object.keys(obj).map(key => transform(obj[key]))
112+
}
113+
114+
// Avoid: Anonymous inline closures when reusable
115+
const result = Object.keys(obj).map(key => transform(obj[key]))
116+
```
117+
* Take advantage of function hoisting to organize code with important functions at the top:
118+
```javascript
119+
// Main functions at the top (these can call utility functions defined below)
120+
function processData(data) {
121+
const validated = validateInput(data)
122+
return transformData(validated)
123+
}
124+
125+
// Utility functions at the bottom
126+
function validateInput(data) {
127+
// Implementation details...
128+
}
129+
130+
function transformData(data) {
131+
// Implementation details...
132+
}
133+
```
134+
135+
### Comments & Documentation
136+
137+
* Use `/* Section */` for grouping related functions
138+
* Use `// comment` for single-line comments
139+
* Mark unfinished code with `// TODO: description`
140+
* Document abbreviations with "Short for" comments
141+
* Document performance optimizations with comments
142+
* Document edge cases and platform-specific behavior
143+
144+
### Import & Export Patterns
145+
146+
* Group imports at the top of files
147+
* Use consistent single-letter aliases (l for lang, i for iter, etc.)
148+
* Use named exports only, never default exports
149+
* Export each function individually rather than in groups
150+
* Maintain minimal cross-module dependencies
151+
152+
## Design Guidelines for New Code
153+
154+
### Function Signatures
155+
* Use standard variable and parameter names based on role and type:
156+
157+
**For role-based variables (prefer these):**
158+
* `val` for generic values (when no better name exists)
159+
* `src` for source data in transformations
160+
* `tar` for targets of modification
161+
* `mod` for modifications
162+
* `opt` for options
163+
* `ctx` for context
164+
* `acc` for accumulator
165+
166+
**For type-based variables (when role isn't clear):**
167+
* `str` for strings
168+
* `num` for numbers
169+
* `obj` for objects
170+
* `arr` for arrays
171+
172+
**For domain-specific variables**, use short but descriptive names without abbreviation requirements
173+
174+
**Exceptions:** Single-letter import aliases (`l`, `i`, `o`, `s`, etc.) and the element creator `E`
175+
176+
See guidelines.md for the complete list
177+
* Functions should be small, simple, focused and do one thing well
178+
* Functions should have single purposes with descriptive names
179+
* Follow established patterns (`is*`, `req*`, `opt*`, `only*`, etc.)
180+
* Avoid parameter defaults; use separate functions for variant behavior
181+
182+
### Error Handling
183+
* Validate parameters early (fail fast)
184+
* Throw descriptive TypeErrors with informative messages
185+
* Include value type and expected type in error messages
186+
* Use error factories from `lang.mjs` when possible
187+
188+
### Documentation Style
189+
* Add inline comments for non-obvious code and optimizations
190+
* Mark unfinished code with `TODO` comments
191+
* Group related functions with section comments
192+
* Document edge cases and platform-specific behavior
193+
* Each exported function/class should have a clear purpose
194+
* Documentation lives in `/doc/` and is compiled to `/docs/`
195+
* Include examples for non-obvious functionality
196+
197+
### Type Safety
198+
* Prefer tight constraints with explicit validation
199+
* Validate parameters before using them
200+
* Use dedicated type validators from `lang.mjs`
201+
* Use the consistent pattern for type validation variants:
202+
* `isThing` — Returns boolean indicating if value matches type
203+
* `reqThing` — Requires value to match type, throws otherwise
204+
* `optThing` — Accepts the type or null/undefined
205+
* `onlyThing` — Returns value if it matches type, otherwise undefined
206+
* `laxThing` — Returns default value if nil, otherwise requires type
207+
* Place validation at the beginning of functions with early returns
208+
209+
### Return Values
210+
* Functions return validated value when successful
211+
* Methods often return `this` for chaining
212+
* Transformational functions return new values (don't modify inputs)
213+
* Mutation functions should be explicit and return the modified object
214+
215+
### Code Organization
216+
* Group related functions together
217+
* Keep internal helpers private (non-exported)
218+
* Separate type checking from business logic
219+
* Use small, focused classes with single responsibilities
220+
221+
### Performance Considerations
222+
* Prioritize performance with carefully documented optimizations
223+
* Use null prototype objects with `Emp()` for dictionaries instead of plain objects
224+
* Minimize unnecessary object creation and property access
225+
* Use symbols for private properties
226+
* Prefer while loops with pre-increment for performance-critical iterations:
227+
```javascript
228+
// More efficient loop with cached length and pre-increment
229+
let ind = -1
230+
const len = arr.length
231+
while (++ind < len) {
232+
const val = arr[ind]
233+
// Process val
234+
}
235+
```
236+
* Cache array/string length in variables when iterating in loops
237+
* Consider memory usage and garbage collection patterns
238+
* Use `isX` type checks before expensive operations when possible
239+
* Consider adding `// TODO tune perf` comments for future optimization points
240+
241+
### Class Implementation
242+
* Use class declarations for objects with behavior
243+
* Extend `l.Emp()` instead of `Object` for lightweight inheritance
244+
* Use method shorthand syntax in class bodies
245+
* Use explicit getter/setter syntax for properties with computed values
246+
* Define instance methods with standard syntax, not arrow functions
247+
* Use static methods for factory patterns and class-related utilities
248+
* Make chainable methods return `this`
249+
250+
## Build & Test Commands
251+
252+
```sh
253+
# Testing
254+
make test # Run all tests
255+
make test_w # Watch mode for tests
256+
make feat=lang test # Test specific module
257+
258+
# Linting
259+
make lint # Run linters (deno & eslint)
260+
make lint_w # Watch mode for linting
261+
262+
# Documentation
263+
make doc # Generate documentation
264+
make doc_w # Watch mode for documentation
265+
266+
# Development
267+
make watch # Watch mode for tests, lint, and docs
268+
make prep # Test, lint, and generate docs
269+
270+
# Publishing
271+
make pub # Tag and push for publishing
272+
```
273+
274+
## Testing Approach
275+
276+
* Uses a custom test framework defined in `test.mjs`
277+
* Each module has corresponding test file in `/test/` directory
278+
* Test files follow naming pattern: `modulename_test.mjs`
279+
* Test assertions use `t.eq()`, `t.ok()`, `t.throws()`, etc.
280+
* Benchmarks are available with `make bench`
281+
282+
### Testing Best Practices
283+
284+
* Test type validations with both valid and invalid inputs
285+
* Test error handling with expected exceptions
286+
* Verify immutability of operations where expected
287+
* Use `t.is` for comparing primitive values (numbers, strings, booleans)
288+
* Use `t.eq` only for deep equality comparisons of objects and arrays
289+
* Test both normal and edge cases for functions
290+
* Test compatibility across supported environments

cli.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function emptty(soft) {
3636
const Deno = globalThis.Deno
3737

3838
if (Deno?.isatty) {
39-
if (Deno.isatty()) {
39+
if (Deno.isatty(Deno.stdout.rid)) {
4040
Deno.stdout.writeSync(soft ? arrClearSoft() : arrClearHard())
4141
return true
4242
}

doc/cmd_doc.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as p from '../path.mjs'
66
const VER = (await io.readJson(`package.json`)).version
77
const CLI = cl.Flag.os()
88
const WATCH = CLI.boolOpt(`watch`)
9+
const CLEAR = CLI.boolOpt(`clear`)
910
const RE_WATCH = /(?:^doc[/](?:\w+[/])*)\w+[.]md|(?:\w+[.]mjs$)/
1011
const DIR_DOC_SRC = `doc`
1112
const DIR_DOC_OUT = `docs`
@@ -350,7 +351,7 @@ async function main() {
350351
await runTimedOpt()
351352

352353
for await (const _ of io.filterWatch(io.watchCwd(), allow)) {
353-
cl.emptty()
354+
if (CLEAR) cl.emptty()
354355
await runTimedOpt()
355356
}
356357
}

doc/dom_reg_readme.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ Example mockup for a pushstate link.
2626
```js
2727
import * as dr from '{{featUrl dom_reg}}'
2828

29+
// Enables immediate registration.
30+
// By default, registration is deferred for SSR compatibility.
31+
dr.Reg.main.setDefiner(customElements)
32+
2933
// Immediately ready for use. Tag is automatically set to `a-btn`.
30-
class Btn extends dr.HTMLButtonElement {
34+
// The mixin `MixReg` enables automatic registration on instantiation.
35+
class Btn extends dr.MixReg(HTMLButtonElement) {
3136
constructor(text) {
3237
super()
3338
this.textContent = text
@@ -37,7 +42,7 @@ class Btn extends dr.HTMLButtonElement {
3742
document.body.append(new Btn(`click me`))
3843

3944
// Immediately ready for use. Tag is automatically set to `my-link`.
40-
class MyLink extends dr.HTMLAnchorElement {
45+
class MyLink extends dr.MixReg(HTMLAnchorElement) {
4146
constructor(text, href) {
4247
super()
4348
this.textContent = text

doc/lang/is.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Identity test: same as `===`, but considers `NaN` equal to `NaN`. Equivalent to [_SameValueZero_](https://www.ecma-international.org/ecma-262/6.0/#sec-samevaluezero) as defined by the language spec. Used internally for all identity tests.
22

3-
Note that [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) implements [_SameValue_](https://www.ecma-international.org/ecma-262/6.0/#sec-samevalue), which treats `-0` and `+0` as _distinct values_. This is typically undesirable. As a result, you should prefer `l.is` over `===` or `Object.is`.
3+
Note that [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) implements [_SameValue_](https://www.ecma-international.org/ecma-262/6.0/#sec-samevalue), which treats `-0` and `+0` as _distinct values_. This is typically undesirable. As a result, you should prefer `l.is` over `===` or `Object.is` unless you _know_ you intend to differentiate `-0` and `+0`.
44

55
```js
66
import * as l from '{{featUrl lang}}'

doc/lang/isFin.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as l from '{{featUrl lang}}'
66
l.isFin(1)
77
// true
88

9-
l.isFin('1')
9+
l.isFin(`1`)
1010
// false
1111

1212
l.isFin(NaN)

doc/lang/isNum.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as l from '{{featUrl lang}}'
66
l.isNum(1)
77
// true
88

9-
l.isNum('1')
9+
l.isNum(`1`)
1010
// false
1111

1212
l.isNum(NaN)

doc/lang/req.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ import * as l from '{{featUrl lang}}'
88
l.req({one: `two`}, l.isObj)
99
// {one: `two`}
1010

11-
l.req('str', l.isFun)
11+
l.req(`str`, l.isFun)
1212
// Uncaught TypeError: expected variant of isFun, got "str"
1313
```

0 commit comments

Comments
 (0)