airier 0.0.10 → 0.1.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/AGENTS.md CHANGED
@@ -1,168 +1,185 @@
1
1
  # AI Coding Assistant Instructions
2
2
 
3
- Write code that breathes. Think Ruby-like elegance meets modern JavaScript. Every character should earn its place.
3
+ Write code that breathes. Think Ruby-like elegance meets modern js.
4
4
 
5
- ## Philosophy: Airy, Minimalist Code
5
+ Important boundaries:
6
6
 
7
- - **Minimalism First**: Don't add features that weren't requested. Three similar lines beats a premature abstraction. If unused, delete completely.
8
- - **Readability Through Spacing**: Code needs room to breathe. Spacing makes structure visible at a glance.
9
- - **Modern JavaScript Only**: Use `const`/`let`, arrow functions, destructuring, template literals, ES modules.
7
+ - You do not touch files specified in `.gitignore`
8
+ - You do not touch `.env` or `.env.*` files
9
+ - You do not touch dotfiles, pattern `.*`
10
10
 
11
- ## Critical Style Rules
11
+ ==============================
12
12
 
13
- ### 1. No Semicolons (Non-negotiable)
14
- ```javascript
15
- // ✅ CORRECT
16
- const x = 5
17
- return x + y
13
+ ## 1. Code Philosophy
18
14
 
19
- // WRONG
20
- const x = 5;
21
- return x + y;
22
- ```
15
+ **Code is written for human eyes**: Code is a description of the intent of a programmer, it must communicate the intent. For machines, the syntax and style is not relevant. Therefore, code style must be optimised for human eyes and mental models.
23
16
 
24
- ### 2. Spacing Everywhere
25
- ```javascript
26
- // ✅ CORRECT - Airy and readable
27
- const arr = [ 1, 2, 3 ]
28
- const obj = { foo: 'bar' }
29
- const result = my_func( arg1, arg2 )
30
- if( condition ) {
31
- console.log( `Value: ${ variable }` )
32
- }
33
- const arrow = ( a, b ) => a + b
34
-
35
- // ❌ WRONG - Cramped
36
- const arr = [1, 2, 3]
37
- const obj = {foo: 'bar'}
38
- const result = myFunc(arg1, arg2)
39
- if(condition) {
40
- console.log(`Value: ${variable}`)
41
- }
42
- ```
17
+ **More comments is better than less comments**: Reading code can clarify function but not intention. Comments are a reminder to self, or an explanation to coworkers.
43
18
 
44
- ### 3. snake_case for Everything
45
- ```javascript
46
- // CORRECT
47
- const timeout_ms = 5000
48
- const user_name = 'John'
49
- const add_numbers = ( a, b ) => a + b
50
- const fetch_user_data = async ( user_id ) => { }
19
+ **Readability Through Spacing**: Code needs room to breathe. Spacing makes structure visible at a glance.
20
+
21
+ **Remember**: Code is read far more than it's written. Make it beautiful, make it breathe, make it obvious.
51
22
 
52
- // ❌ WRONG - camelCase
53
- const timeoutMs = 5000
54
- const userName = 'John'
55
- const addNumbers = ( a, b ) => a + b
56
- const fetchUserData = async ( userId ) => { }
23
+ ==============================
24
+
25
+ ## 2. Syntax preferences
26
+
27
+ Code should be elegant, not use superfluous characters, and have space to breathe. For example: do not use semicolons, add space in brackets. The linter will show you syntax preferences, to that end with every change learn the styling by:
28
+
29
+ 1. save your changes and look at the syntax
30
+ 2. run `npm run lint` and ignore the command output
31
+ 3. look at how the linter changed the style, and mimic it in future responses.
32
+
33
+ ==============================
34
+
35
+ ## 3. Tooling preferences
36
+
37
+ ### LLM agent context
38
+
39
+ Any LLM including you should always:
40
+
41
+ - Read the `AGENTS.md` file for instructions
42
+ - Check if `SPECIFICATION.md` exists, if so read it
43
+ - Check if `SPECIFICATION.*.md` patterned files exist, if so read them for context
44
+ - Check for all `*.md` files in the project root
45
+
46
+ ### Node.js usage
47
+
48
+ - Use `nvm` for version management, with the latest LTS (24) in the `.nvmrc`
49
+ - Do not modify files in `node_modules/`
50
+ - Environment variables are stored in `.env` which is supported by node without dependencies
51
+ - Frontend code should use Vite for bundling
52
+ - Backend code should use Node.js
53
+ - Prefer javascript over typescript, including when setting up vite projects
54
+
55
+ ### React usage
56
+
57
+ - Frontends should be built in react
58
+ - React should be used in frontend mode (no server components)
59
+ - Routing is done with `react-router` BrowserRouter
60
+ - State is put in the URL where possible using the `use-query-params` npm package
61
+ - State that is used in multiple places at once uses `zustand`
62
+ - Components follow a structure inspired by Atomic Design where they are split into:
63
+ - Atoms: stateless components
64
+ - Molecules: stateful components (may use Atoms)
65
+ - Pages: components rendered by the router
66
+
67
+ File structure in a react project:
68
+
69
+ ```bash
70
+ .
71
+ ├── assets
72
+ ├── package-lock.json
73
+ ├── package.json
74
+ ├── public
75
+ │   ├── assets
76
+ │   ├── favicon.ico
77
+ │   ├── logo192.png
78
+ │   ├── logo512.png
79
+ │   └── robots.txt
80
+ ├── src
81
+ │   ├── App.jsx
82
+ │   ├── components
83
+ │   │   ├── atoms
84
+ │   │   ├── molecules
85
+ │   │   └── pages
86
+ │   ├── hooks
87
+ │   ├── index.css
88
+ │   ├── index.jsx
89
+ │   ├── modules
90
+ │   ├── routes
91
+ │   │   └── Routes.jsx
92
+ │   └── stores
93
+ └── vite.config.js
57
94
  ```
58
95
 
59
- ### 4. Keywords: No Space After
60
- ```javascript
61
- // ✅ CORRECT
62
- if( x > 5 ) { }
63
- for( let i = 0; i < 10; i++ ) { }
64
- while( condition ) { }
96
+ ### Using Mentie Helpers
65
97
 
66
- // WRONG
67
- if (x > 5) { }
68
- for (let i = 0; i < 10; i++) { }
69
- ```
98
+ If `mentie` is installed, **always use its utilities**. Check `node_modules/mentie/index.js` for available exports.
70
99
 
71
- ### 5. Indentation: 4 Spaces
72
- ```javascript
73
- // ✅ CORRECT
74
- const process_data = ( data ) => {
75
- const filtered = data.filter( item => item.active )
76
- return filtered
77
- }
100
+ ```js
101
+ import { log, multiline_trim, shuffle_array } from 'mentie'
78
102
 
79
- // WRONG - 2 spaces
80
- const process_data = ( data ) => {
81
- const filtered = data.filter( item => item.active )
82
- return filtered
83
- }
103
+ log.info( `User logged in:`, user_id )
104
+
105
+ const query = multiline_trim( `
106
+ SELECT * FROM users
107
+ WHERE active = true
108
+ ` )
109
+
110
+ const randomized = shuffle_array( items )
84
111
  ```
85
112
 
86
- ## Spacing Quick Reference
113
+ ==============================
87
114
 
88
- | Element | Pattern | Example |
89
- |---------|---------|---------|
90
- | Arrays | `[ items ]` | `[ 1, 2, 3 ]` |
91
- | Objects | `{ key: value }` | `{ foo: 'bar' }` |
92
- | Function calls | `func( args )` | `my_func( a, b )` |
93
- | Arrow functions | `( args ) => { }` | `const fn = ( x ) => x * 2` |
94
- | Template literals | `${ expr }` | `` `Hello ${ name }` `` |
95
- | Blocks | `keyword( cond ) {` | `if( x > 5 ) {` |
115
+ ## 3. Code style preferences
96
116
 
97
- ## React Specific
98
- ```javascript
99
- // CORRECT
100
- <Component prop={ value } />
101
- <div className="foo">{ children }</div>
117
+ ### Always use template literals instead of strings
118
+ ```js
119
+ // Use literals for regular strings
120
+ const name = `Ada Localace`
102
121
 
103
- // WRONG
104
- <Component prop={value} />
105
- <div className="foo">{children}</div>
122
+ // Use templates for string manipulation too
123
+ const annotated_name = `${ name } ${ Math.random() }`
106
124
  ```
107
125
 
108
- - No `import React from 'react'` needed (modern React)
109
- - Prefer functional components with hooks
110
- - PropTypes optional (use TypeScript if you need types)
111
126
 
112
- ## Minimalism Rules
127
+ ### snake_case for Everything
128
+ ```js
129
+ const timeout_ms = 5_000
130
+ const user_name = 'John'
131
+ const fetch_user_data = async ( user_id ) => { }
132
+ ```
113
133
 
114
- ### Don't Over-Engineer
115
- ```javascript
116
- // WRONG
117
- const create_greeting = ( config ) => {
118
- const { name, formal = false } = config
119
- return formal ? `Hello, ${ name }` : `Hey ${ name }`
120
- }
134
+ ### Use comments to describe intent
135
+ ```js
136
+ import { abort_controller } from 'mentie'
121
137
 
122
- // CORRECT
123
- const greet = ( name ) => `Hello, ${ name }`
138
+ // Load the users with a timeout to prevent hanging
139
+ const fetch_options = abort_controller( { timeout_ms: 10_000 } )
140
+ const { uids } = await fetch( 'https://...', fetch_options ).then( res => res.json() )
141
+
142
+ // Parallel fetch resulting data to optimise speed
143
+ const downstream_data = await Promise.all( uids.map( async uid => fetch( `https://...?uid=${ uid }` ) ) )
124
144
  ```
125
145
 
126
- ### ❌ Don't Add Unrequested Features
127
- ```javascript
128
- // ❌ WRONG - validation, logging, backup not requested
129
- const save_user = ( user, options = {} ) => {
130
- if( options.validate ) validate( user )
131
- if( options.log ) console.log( user )
132
- return db.save( user )
133
- }
134
146
 
135
- // CORRECT
136
- const save_user = ( user ) => db.save( user )
147
+ ### Prioritise semantic clarity over optimisation
148
+ Don't reassign variables. Create new bindings for each transformation step.
149
+ ```js
150
+ // Parse a dataset - each step is clear and traceable
151
+ const data = []
152
+ const filtered_data = data.filter( ( { relevant_number } ) => relevant_number > 1.5 )
153
+ const restructured_data = filtered_data.map( ( { base_value, second_value } ) => ( { composite_value: base_value * second_value } ) )
154
+ return restructured_data
155
+ ```
156
+
157
+
158
+ ### Lean towards onelining single statements
159
+ Single statements can be on one line. Multiple statements need blocks.
160
+ ```js
161
+ // ✅ Single statement - oneline it
162
+ if( condition ) log.info( `Message` )
163
+ const filtered_data = data.filter( ( { relevant_property } ) => relevant_property )
137
164
  ```
138
165
 
139
- ## Functional Programming Over Loops
140
166
 
141
- Prefer higher-level array methods over primitive loops. They're more readable and declarative.
167
+ ### Functional Programming Over Loops
168
+
169
+ Prefer `.map()`, `.filter()`, `.reduce()`, `.find()`, `.some()`, `.every()` over `for`/`while` loops.
142
170
 
143
- ```javascript
144
- // ✅ CORRECT - Functional approach
171
+ ```js
145
172
  const active_users = users.filter( u => u.active )
146
- const user_names = users.map( u => u.name )
147
- const total = numbers.reduce( ( sum, n ) => sum + n, 0 )
148
-
149
- // ❌ WRONG - Primitive loops
150
- const active_users = []
151
- for( let i = 0; i < users.length; i++ ) {
152
- if( users[i].active ) {
153
- active_users.push( users[i] )
154
- }
155
- }
173
+ const user_names = active_users.map( u => u.name )
174
+ const total_age = user_names.reduce( ( sum, age ) => sum + age, 0 )
156
175
  ```
157
176
 
158
- **Prefer in order**: `.map()`, `.filter()`, `.reduce()`, `.find()`, `.some()`, `.every()` over `for`/`while` loops.
159
177
 
160
- ## JSDoc for Exported Functions
178
+ ### JSDoc for Exported Functions
161
179
 
162
- **CRITICAL**: Every exported function MUST have JSDoc comments. Before finishing, always verify JSDoc is present and up-to-date for all changed exports.
180
+ **CRITICAL**: Every exported function MUST have JSDoc. Verify before finishing!
163
181
 
164
- ```javascript
165
- // ✅ CORRECT
182
+ ```js
166
183
  /**
167
184
  * Fetches user data from the API
168
185
  * @param {string} user_id - The ID of the user to fetch
@@ -172,148 +189,127 @@ export const fetch_user = async ( user_id ) => {
172
189
  const response = await api.get( `/users/${ user_id }` )
173
190
  return response.data
174
191
  }
175
-
176
- // ❌ WRONG - No JSDoc
177
- export const fetch_user = async ( user_id ) => {
178
- const response = await api.get( `/users/${ user_id }` )
179
- return response.data
180
- }
181
192
  ```
182
193
 
183
- **Before completing any task**: Review all modified exports and ensure their JSDoc is accurate and current.
194
+ ### Error Handling
184
195
 
185
- ## Error Handling
196
+ Only at boundaries (user input, external APIs). Trust internal code. Remember `finally` for cleanup!
186
197
 
187
- Only at boundaries (user input, external APIs). Trust internal code.
188
-
189
- **IMPORTANT**: Remember the `finally` block! It's perfect for cleanup operations and runs regardless of success/failure.
190
-
191
- ```javascript
192
- // ✅ CORRECT - Using finally for cleanup
198
+ ```js
193
199
  const fetch_user = async ( id ) => {
194
- const loading_indicator = start_loading()
195
200
 
196
201
  try {
202
+ start_loading()
197
203
  const response = await api.get( `/users/${ id }` )
198
204
  return response.data
199
205
  } catch( error ) {
200
206
  throw new Error( `Failed to fetch user: ${ error.message }` )
201
207
  } finally {
202
- // Always runs - perfect for cleanup
203
- stop_loading( loading_indicator )
208
+ stop_loading()
204
209
  }
205
210
  }
211
+ ```
206
212
 
207
- // Also valid - Simple boundary error handling
208
- const get_user = async ( id ) => {
209
- try {
210
- const response = await api.get( `/users/${ id }` )
211
- return response.data
212
- } catch( error ) {
213
- throw new Error( `Failed to fetch user: ${ error.message }` )
214
- }
215
- }
213
+ ### Complete example of well styled code
214
+
215
+ ```js
216
+ /**
217
+ * Fetches and processes active users from the API
218
+ * @param {Object} options
219
+ * @param {Array} options.user_ids - User ids to fetch
220
+ * @param {Number} options.limit - Limit the amount of users to fetch
221
+ * @returns {Promise<Array>} Processed user objects
222
+ */
223
+ export async function fetch_and_process_users( { user_ids, limit=5 } = {} ) {
224
+
225
+ // Get users to ensure up to date data
226
+ const users = await api.get( `/users`, { user_ids, limit } )
216
227
 
217
- // Internal functions trust their callers
218
- const process_user = ( user ) => user.name.toUpperCase()
228
+ // Keep only active users to prevent wasting time on inactive ones
229
+ const filtered_users = users.filter( ( { active } ) => active )
219
230
 
220
- // WRONG - Defensive programming everywhere
221
- const process_user = ( user ) => {
222
- if( !user || !user.name ) return null
223
- return user.name.toUpperCase()
231
+ // Annotate users with value based on local conversion so we can show the user the computed values
232
+ const annotated_users = filtered_users.map( ( { score, user } ) => ( { score: score * local_conversion_value, ...user } ) )
233
+
234
+ // Return users with annotated data
235
+ return annotated_users
224
236
  }
225
- ```
226
237
 
227
- ## Using Mentie Helpers (If Installed)
238
+ ```
228
239
 
229
- If the `mentie` package is installed in the project, **always use its utilities** instead of reinventing them.
230
240
 
231
- **IMPORTANT**: Check `node_modules/mentie/index.js` (or the package's main export file) to see all available exports and their usage before implementing any utility functions yourself.
241
+ ### React Specific styling
232
242
 
233
- ### Logging with `log`
234
- ```javascript
235
- import { log } from 'mentie'
243
+ **Exception to snake_case**: React component names MUST be PascalCase (required by React/JSX).
236
244
 
237
- // ✅ CORRECT - Use mentie's structured logging
238
- log.info( 'User logged in:', user_id )
239
- log.debug( 'Processing data:', data )
240
- log.insane( 'Detailed trace:', complex_object )
245
+ ```js
246
+ // Components are PascalCase
247
+ const UserProfile = ( { user_id } ) => { }
248
+ const DataTable = () => { }
241
249
 
242
- // WRONG - Don't use console.log when mentie is available
243
- console.log( 'User logged in:', user_id )
250
+ // Everything else is snake_case
251
+ const user_name = `John`
252
+ const fetch_user_data = async ( user_id ) => { }
244
253
  ```
245
254
 
246
- ### String Utilities
247
- ```javascript
248
- import { multiline_trim } from 'mentie'
255
+ **File naming**: Component files use PascalCase to match component name.
256
+ - `UserProfile.jsx` - Component files
257
+ - `fetch_user_data.js` - Utility files
249
258
 
250
- // ✅ CORRECT - Clean multiline strings
251
- const query = multiline_trim( `
252
- SELECT *
253
- FROM users
254
- WHERE active = true
255
- ` )
259
+ **Event handlers**:
260
+ - Do not name things after the event (e.g. `on_click`) but name them actions (e.g. `save_user`)
256
261
 
257
- // WRONG - Manual string manipulation
258
- const query = `SELECT * FROM users WHERE active = true`
259
- ```
262
+ **JSX spacing and structure**:
263
+ ```js
264
+ // Styled and stateless sections use arrow functions or variables
265
+ const Header = styled.aside`
266
+ color: red;
267
+ `
260
268
 
261
- ### Array Utilities
262
- ```javascript
263
- import { shuffle_array } from 'mentie'
269
+ // Use JSX comments to separate sections
270
+ export function UserProfile( { user_id, on_update } ) {
264
271
 
265
- // CORRECT - Use mentie's shuffle
266
- const randomized = shuffle_array( items )
272
+ // Hooks at the top
273
+ const [ user_data, set_user_data ] = useState( null )
274
+ const [ is_loading, set_is_loading ] = useState( false )
267
275
 
268
- // WRONG - Implementing your own shuffle
269
- const randomized = items.sort( () => Math.random() - 0.5 )
270
- ```
276
+ // Effects after hooks
277
+ useEffect( () => {
278
+ fetch_user_data( user_id ).then( set_user_data )
279
+ }, [ user_id ] )
271
280
 
272
- **Check for mentie**:
273
- 1. Look in `package.json` dependencies to see if mentie is installed
274
- 2. If present, read `node_modules/mentie/index.js` to see all available exports
275
- 3. Use mentie's utilities instead of reimplementing them
281
+ // Event handlers
282
+ const update_user = () => on_update( user_data )
276
283
 
277
- ## Complete Example
284
+ // Conditional rendering
285
+ if( is_loading ) return <LoadingSpinner />
278
286
 
279
- ```javascript
280
- /**
281
- * Fetches and processes users from the API
282
- * @param {Object} filters - Query filters to apply
283
- * @returns {Promise<Array>} Processed user objects with id, name, and email
284
- */
285
- const fetch_and_process_users = async ( filters = {} ) => {
286
- const users = await api.get( '/users', { params: filters } )
287
-
288
- const processed = users
289
- .filter( user => user.active )
290
- .map( user => ( {
291
- id: user.id,
292
- name: user.name,
293
- email: user.email
294
- } ) )
295
-
296
- return processed
287
+ // Do not add () around returned jsx.
288
+ return <>
289
+
290
+ { /* Profile header section */ }
291
+ <Header title={ user_data.name } />
292
+
293
+ { /* Profile details */ }
294
+ <div className="profile">
295
+ { user_data.details }
296
+ </div>
297
+
298
+ { /* Actions */ }
299
+ <Button on_click={ update_user }>Save</Button>
300
+
301
+ </>
297
302
  }
298
303
 
299
- export default fetch_and_process_users
304
+ // Lists need keys
305
+ const user_list = users.map( ( user ) => <UserCard key={ user.id } user={ user } /> )
306
+
307
+ // Props always have spacing
308
+ <Component prop={ value } />
309
+ <div className="foo">{ children }</div>
300
310
  ```
301
311
 
302
- ## Checklist
303
312
 
304
- - [ ] No semicolons
305
- - [ ] Spacing in all brackets: `[ ]`, `{ }`, `( )`, `${ }`
306
- - [ ] snake_case for all variables and functions
307
- - [ ] 4-space indentation
308
- - [ ] `const`/`let` only (never `var`)
309
- - [ ] Arrow functions preferred
310
- - [ ] Higher-level functions (.map, .filter, .reduce) over loops
311
- - [ ] JSDoc on ALL exported functions (verify before finishing!)
312
- - [ ] Consider `finally` block for cleanup in try/catch
313
- - [ ] Check if `mentie` is installed and use its helpers (log, multiline_trim, etc.)
314
- - [ ] No unnecessary features or abstractions
315
- - [ ] Dead code completely removed
316
313
 
317
- ---
318
314
 
319
- **Remember**: Code is read far more than it's written. Make it beautiful, make it breathe, make it obvious.
315
+
package/README.md CHANGED
@@ -18,7 +18,7 @@ npm pkg set scripts.lint="eslint --fix src"
18
18
  npx husky init
19
19
 
20
20
  # Download files (using new ESLint 9 flat config format)
21
- curl https://raw.githubusercontent.com/actuallymentor/airier/main/eslint.config.js --output eslint.config.js
21
+ curl https://raw.githubusercontent.com/actuallymentor/airier/main/eslint.config.example.js --output eslint.config.js
22
22
  curl https://raw.githubusercontent.com/actuallymentor/airier/main/.vscode/settings.json --output .vscode/settings.json
23
23
  curl https://raw.githubusercontent.com/actuallymentor/airier/main/.husky/pre-commit --output .husky/pre-commit
24
24
  curl https://raw.githubusercontent.com/actuallymentor/airier/main/.babelrc --output .babelrc
@@ -10,6 +10,16 @@ import styleguide from './styleguide.js'
10
10
 
11
11
  // Export the flat config as an array
12
12
  export default [
13
+ // Global ignores for build output and dependencies
14
+ {
15
+ ignores: [
16
+ '**/dist/**',
17
+ '**/build/**',
18
+ '**/node_modules/**',
19
+ '**/.next/**',
20
+ '**/coverage/**'
21
+ ]
22
+ },
13
23
  // Apply to all JavaScript and JSX files
14
24
  {
15
25
  files: [ '**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs' ],
@@ -19,6 +29,9 @@ export default [
19
29
  parserOptions: {
20
30
  ecmaVersion: 2020,
21
31
  requireConfigFile: false,
32
+ babelOptions: {
33
+ presets: [ '@babel/preset-react' ]
34
+ },
22
35
  ecmaFeatures: {
23
36
  jsx: true
24
37
  }
@@ -38,7 +51,7 @@ export default [
38
51
 
39
52
  settings: {
40
53
  react: {
41
- version: 'detect'
54
+ version: '19'
42
55
  }
43
56
  },
44
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airier",
3
- "version": "0.0.10",
3
+ "version": "0.1.0",
4
4
  "description": "Opinionated Eslint and VSCode style guide",
5
5
  "type": "module",
6
6
  "main": "index.js",