Friction Source
Surfaced 2026-05-13 by @tobiu during the #11187 archive substrate review:
"i see minor code smells like:
> version : version,> filename : filename,
> itemCount : itemCount,
> itemIndex : itemIndex
> ```
> => we can just use keys. could be a tech debt radar ticket, since this happens in many spots now."
ES2015 shorthand ({version, filename, itemCount, itemIndex}) is the idiomatic form when an object literal key matches an identifier already in scope. The aligned key : key, form was a pre-ES2015 pattern that accumulated through harness iterations and is now visually dominant in some hot paths.
<h2 class="neo-h2" data-record-id="3">Empirical Prevalence</h2>
Quick grep against the codebase (Perl back-reference, padded and unpadded forms):bash
find ai src test/playwright -name ".mjs" | xargs perl -ne 'print if /^\s+(\w+)\s:\s*\1\s*[,}]/'
- **71 candidate instances** across `ai/`, `src/`, `test/playwright/`
- Concentrated in: `IssueService.mjs`, `DiscussionSyncer.mjs`, `DatabaseService.mjs`, `GraphService.mjs`, `MemorySessionIngestor.mjs`, `AuthService.mjs`, and a few `src/calendar/`, `src/main/`, `src/canvas/`, `src/tab/` files
- Note: 71 is a lower bound — the regex only catches identifier-equals-identifier; doesn't catch `key: this.key` or destructure-rename opportunities
<h2 class="neo-h2" data-record-id="4">The Problem</h2>
1. **Visual noise** — aligned `key : key,` blocks pad horizontally with redundant tokens that ES2015 lets us delete cleanly
2. **Drift risk** — when a variable rename misses a key (e.g. `filename → filepath`), the silent mismatch isn't caught by the literal form; shorthand syntax prevents this class of drift mechanically
3. **MX friction** — agents reading these hot paths waste tokens parsing redundant tokens
<h2 class="neo-h2" data-record-id="5">The Architectural Reality</h2>
Modern V8 handles shorthand identically to the verbose form (no runtime cost difference). The reason both forms coexist is just author-habit accumulation, not any deliberate substrate choice.
ESLint has `object-shorthand` rule (set to `'always'` or `'properties'`) that mechanically enforces the shorthand form on lint.
<h2 class="neo-h2" data-record-id="6">The Fix</h2>
Two-phase approach:
1. **Mechanical sweep** — run codemod or careful regex-replace over the 71 instances, producing a single grouped PR per directory (`ai/`, `src/calendar/`, `src/main/`, etc.) to keep diff review tractable.
2. **Permanent gate** — add `object-shorthand: ['error', 'always']` to the relevant `.eslintrc` so future authors get a lint-error instead of accumulating new instances.
Net byte savings ~500-1500 bytes across the codebase; primary benefit is the lint-gate against future drift.
<h2 class="neo-h2" data-record-id="7">Acceptance Criteria</h2>
- [ ] **(AC1)** Mechanical sweep replaces verbose `key: key` form with shorthand `{key}` across the ~71 identified instances
- [ ] **(AC2)** ESLint `object-shorthand: ['error', 'always']` rule added to project `.eslintrc` (or equivalent flat-config) so the gate is permanent
- [ ] **(AC3)** Lint passes on `dev` after sweep + rule activation
- [ ] **(AC4)** Spot-check audit: no remaining instances detected by `find ai src test/playwright -name "*.mjs" | xargs perl -ne 'print if /^\s+(\w+)\s*:\s*\1\s*[,}]/'` (zero matches expected)
<h2 class="neo-h2" data-record-id="8">Out of Scope</h2>
- Renaming variables to fit shorthand opportunities (e.g., `key: this.foo` → don't rename `this.foo` to `key`); only mechanical key-equals-identifier collapses
- Destructure-rename refactors (`{ foo: bar }` ≠ shorthand candidate)
- The `key : 'literal-string'` form (not a shorthand candidate by definition)
<h2 class="neo-h2" data-record-id="9">Avoided Traps</h2>
- **Hand-edit all 71 in one PR** — too large for review; group by directory
- **Skip the ESLint gate** — the sweep without the gate guarantees the pattern resurfaces within 1-2 release cycles
- **Pre-claim and rush the sweep** — this is a Quick Win lane that any peer can pull; tagged for self-selection
<h2 class="neo-h2" data-record-id="10">Related</h2>
- **Origin friction:** [@tobiu observation 2026-05-13](https://github.com/neomjs/neo/pull/11297#discussion) during <a href="#/news/tickets/11187">#11187</a> archive substrate batch-merge
- **Pattern reference:** ES2015 [Object Initializer / Property Definitions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#property_definitions)
</div>
</div>
</div>
<div id="timeline-11306-1" class="neo-timeline-item event" data-record-id="timeline-11306-1">
<div id="timeline-11306-1-target" class="neo-timeline-badge" ><i class="fa-solid fa-link"></i></div>
<div class="neo-timeline-body">
<a class="neo-timeline-user" href="https://github.com/tobiu" target="_blank">tobiu</a> referenced in commit <code><a href="https://github.com/neomjs/neo/commit/aca8011" target="_blank">aca8011</a></code> - "refactor: collapse 64 <code>key: key</code> shorthand instances + ws cleanup (#11306) (#11926) <span class="neo-timeline-date">on May 24, 2026, 11:36 PM</span>
</div>
</div>
<div id="timeline-11306-2" class="neo-timeline-item event" data-record-id="timeline-11306-2">
<div id="timeline-11306-2-target" class="neo-timeline-badge" style="color: #8250df"><i class="fa-solid fa-circle-check"></i></div>
<div class="neo-timeline-body">
<a class="neo-timeline-user" href="https://github.com/tobiu" target="_blank">tobiu</a> closed this issue <span class="neo-timeline-date">on May 24, 2026, 11:36 PM</span>
</div>
</div></div>
Friction Source
Surfaced 2026-05-13 by @tobiu during the #11187 archive substrate review:
ES2015 shorthand (
{version, filename, itemCount, itemIndex}) is the idiomatic form when an object literal key matches an identifier already in scope. The alignedkey : key,form was a pre-ES2015 pattern that accumulated through harness iterations and is now visually dominant in some hot paths.<h2 class="neo-h2" data-record-id="3">Empirical Prevalence</h2>
Quick grep against the codebase (Perl back-reference, padded and unpadded forms):bash find ai src test/playwright -name ".mjs" | xargs perl -ne 'print if /^\s+(\w+)\s:\s*\1\s*[,}]/'