Skip to content

Add platform axis (web / ios / android / adaptive)#269

Open
vinaypokharkar wants to merge 1 commit into
pbakaus:mainfrom
vinaypokharkar:feat/platform-axis
Open

Add platform axis (web / ios / android / adaptive)#269
vinaypokharkar wants to merge 1 commit into
pbakaus:mainfrom
vinaypokharkar:feat/platform-axis

Conversation

@vinaypokharkar

@vinaypokharkar vinaypokharkar commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Closes #99.

Adds a platform axis orthogonal to register, mirroring the existing brand/product pattern. PRODUCT.md gains a ## Platform field; a missing field defaults to web, so existing projects are unaffected.

What

  • Values: web (default), ios, android, and adaptive for cross-platform apps that ship both from one codebase (Flutter, React Native, KMP). A line naming both targets (e.g. ios, android) is read as adaptive.
  • extractPlatform() in skill/scripts/context.mjs (mirrors extractRegister()); CLI emits a NEXT STEP directive to read the native reference(s) for ios/android, and both for adaptive.
  • New references: reference/web.md (thin pointer), reference/ios.md (Apple HIG distilled), reference/android.md (Material Design 3 distilled), adapted from the MIT-licensed ehmo/platform-design-skills (attributed in NOTICE.md).
  • init.md: platform hypothesis (Flutter/RN/SwiftUI/Compose detection), question after register, PRODUCT.md field, and guidance to choose by the design language the app renders, not the toolchain.
  • Short ## Platform sections in adapt, audit, animate, layout where native guidance diverges.
  • context-signals.mjs exposes platform; SKILL routing marks live and the detect CLI as web-only.
  • CLAUDE.md Platform-axis subsection.

Scope

  • Live mode and the detect CLI stay web-only (per the issue discussion).
  • Platform is a domain axis, not a command → command count stays 23; no count-sync churn.
  • Also includes a small cross-platform fix in scripts/build.js: normalize path separators so the slop-page prose exclusion works on Windows.

Tests

  • tests/context.test.mjs: extractPlatform unit + comma-list + adaptive/ios/web CLI coverage — 39/39 pass.
  • bun run build:skills clean (prose validator + counts green); harness output dirs regenerated.
  • Skill 3.5.0 → 3.6.0; changelog entry added.

Note

Low Risk
Skill and documentation changes with backward-compatible defaults (web); no auth, payment, or runtime API surface changes beyond context parsing and agent guidance.

Overview
v3.9.0 adds a platform axis alongside register: PRODUCT.md gets ## Platform (web / ios / android / adaptive). Missing platform still behaves as web.

extractPlatform() in context.mjs parses the field (including ios, androidadaptive), emits NEXT STEP directives to load native references, and exposes platform in context-signals.mjs. Setup step 5 and routing now load HIG (ios.md) and/or Material 3 (android.md) on top of brand/product; web.md is a thin pointer. Content is adapted from ehmo’s MIT platform-design-skills with attribution in NOTICE.md.

/impeccable init infers platform from the repo, asks after register, writes the field, and skips live recommendations for native targets. adapt, audit, animate, and layout gain ## Platform sections; audit/detect/live are documented as web-only for native projects.

Tests cover extractPlatform, CLI directives, and skill-behavior scenario 10 (iOS loads ios.md). Minor Windows fix in scripts/build.js prose scan path normalization.

Reviewed by Cursor Bugbot for commit ecb8c03. Bugbot is set up for automated code reviews on this repo. Configure here.

@vinaypokharkar vinaypokharkar requested a review from pbakaus as a code owner June 18, 2026 21:34
Comment thread .agents/skills/impeccable/SKILL.md Outdated
Comment thread skill/reference/init.md Outdated
Comment thread skill/scripts/context.mjs
Comment thread .agents/skills/impeccable/reference/init.md Outdated
Comment thread .agents/skills/impeccable/reference/init.md Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4706da2. Configure here.

Comment thread skill/reference/init.md
@pbakaus

pbakaus commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Welcome back, cool to see you picked this up after all!

The plumbing is good, it's a clean mirror of register and the gating and defaults are right. Two conceptual things and two blockers.

On register vs platform: you're right that register is a web concept, brand is the product's landing page (display fonts, go wild), product is the app itself. The question native raises: there are native apps that throw out the platform and do their own layout, UI and fonts, but that's mostly either a game (which the HIG/Material refs can't help with anyway) or just bad practice, so we shouldn't advocate for it. Which means native compresses the register axis: on native, platform conformance is the bar for structure, interaction and accessibility regardless of register, and brand only lives in the expressive layer the platform already gives you (theming, type scale, color, motion). That's why it felt like it should be one value, native really does squeeze register down to "how hard do you lean on the expressive layer," not "do you follow the platform."

I'd still keep two fields (web genuinely needs register, and adaptive has to branch out to both rulebooks), but the native refs should say register's role is narrow here, and right now they contradict each other. ios.md says "native is the opposite of brand," android.md says "let brand express through Material's theming, not by discarding it." android's framing is the right one. ios.md reads as "no brand on native, ever," which is too far the other way (Calm, Duolingo, Spotify all have strong identity inside the platform). Unify both on: earned familiarity is the bar, brand within the rails, never by breaking them.

Separately, ios.md and android.md carry full Accessibility sections and they load at design time (setup step 5). We keep a11y in audit.md on purpose so design-time output doesn't go timid, and this PR already adds native a11y to audit.md's Platform section, so I'd strip it from the design-time refs and let audit own it.

Two blockers regardless of the above:

The branch is 113 commits behind main, bumps to 3.6.0 when main is already at 3.7.1, and conflicts on ~49 files. It needs a rebase before I can really review the build (build:skills currently fails on the branch as-is).

It also commits all the generated provider trees plus ./plugin. Drop those and keep it source-first: just skill/, scripts/, tests/, CLAUDE.md, NOTICE.md, and the changelog/version, and let the sync workflow regenerate the rest.

Smaller: adaptive isn't threaded through everywhere (it was added late, so init Step 2, the Step 7 summary, the CLAUDE.md heading and the per-command divergence notes still say only web/ios/android). And there's no skill-behavior scenario for the platform flow, the extractPlatform unit tests are solid but don't check that the agent actually loads ios.md when platform is ios.

Orthogonal to register: register decides whether design IS or SERVES the
product; platform decides the delivery target and which native conventions
apply. Set `## Platform` in PRODUCT.md; a missing field defaults to `web`,
so legacy projects are unaffected.

- extractPlatform() in skill/scripts/context.mjs (mirrors extractRegister);
  the CLI appends a NEXT STEP directive to read the native reference(s).
  `adaptive` (Flutter / RN / KMP shipping both iOS and Android) loads both
  ios.md and android.md.
- New reference/ios.md (Apple HIG distilled) and reference/android.md
  (Material 3 distilled); reference/web.md is a thin pointer. The native
  refs frame register's role as narrow: platform conformance is the bar,
  brand lives in the expressive layer the platform gives you, never by
  breaking the rails.
- Setup step 5 loads the native reference(s) when platform is native. Live
  mode and the detect CLI stay web-only, gated off ios/android/adaptive.
- init asks platform right after register; adapt/audit/animate/layout carry
  short platform divergence notes; all secondary spots thread `adaptive`.
- a11y stays in audit.md (loading it at design time makes output timid), so
  the native refs carry no Accessibility section; audit.md's Platform
  section owns native a11y.
- Tests: extractPlatform unit coverage + skill-behavior scenario 10
  (PRODUCT.md platform ios -> agent loads ios.md).

Source-first: only skill/, scripts/, tests/, CLAUDE.md, NOTICE.md, the
changelog and version are committed; the sync workflow regenerates the
provider trees and ./plugin on merge.

ios.md / android.md are distilled from the MIT-licensed
ehmo/platform-design-skills; attribution in NOTICE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

Introduces a platform axis (web / ios / android / adaptive) as a new dimension in PRODUCT.md, orthogonal to the existing register axis. The feature is fully backward-compatible — a missing ## Platform field defaults to web and existing projects are unaffected.

  • extractPlatform() is added to context.mjs mirroring extractRegister(); the CLI appends a typed NEXT STEP directive for native projects, and context-signals.mjs exposes setup.platform to the bare-menu routing that now skips live and detect.mjs for native targets.
  • New reference/ios.md (Apple HIG distilled) and reference/android.md (Material Design 3 distilled) are added, MIT-attributed; four sub-command references (adapt, audit, animate, layout) gain short ## Platform divergence sections.
  • A small Windows path-separator fix in scripts/build.js is bundled; extractRegister()'s heading regex is tightened from \b to \s*$ to prevent false matches on headings like ## Register guidelines.

Confidence Score: 4/5

The platform axis is a purely additive, backward-compatible change — missing Platform fields default to web so no existing project is affected.

The implementation is thorough: extractPlatform is logically sound, all edge cases are unit-tested, the CLI directive and signal exposure are consistent, and the SKILL routing for native platforms is correctly applied. The one gap — no integration test asserting the android NEXT STEP directive — is a test coverage omission rather than a functional defect.

tests/context.test.mjs — the CLI integration block tests ios, adaptive, and web but omits an android case; worth adding for symmetry.

Important Files Changed

Filename Overview
skill/scripts/context.mjs Adds extractPlatform() mirroring extractRegister(); also tightens extractRegister heading regex to prevent false matches. Both are well-tested.
skill/scripts/context-signals.mjs Imports and exposes extractPlatform as setup.platform; single-line additive change, no behavioral risk to existing signals.
tests/context.test.mjs Good unit and CLI coverage for extractPlatform; CLI integration tests cover ios, adaptive, and web but are missing the android case.
scripts/build.js Normalizes path separators to POSIX before comparing against excludedPrefixes, fixing the slop-page exclusion on Windows.
skill/SKILL.src.md Adds platform step 5; routes live and detect.mjs as web-only when setup.platform is native. Numbering and routing logic are consistent.
skill/reference/ios.md New file: distilled Apple HIG reference covering layout, touch targets, typography, color/materials, components, and motion.
skill/reference/android.md New file: distilled Material Design 3 reference with full attribution.
skill/reference/init.md Platform hypothesis added to Steps 2-3, Platform field added to PRODUCT.md template, Step 7 updated to skip live for native.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[PRODUCT.md loaded] --> B{Platform section?}
    B -- absent --> C[null treated as web]
    B -- present --> D{First non-empty line}
    D -- web --> E[return web]
    D -- ios --> F[return ios]
    D -- android --> G[return android]
    D -- adaptive --> H[return adaptive]
    D -- ios and android --> H
    D -- other --> I[return null, web default]
    F --> J{CLI output}
    G --> J
    H --> J
    J -- ios --> K[NEXT STEP: read reference/ios.md]
    J -- android --> L[NEXT STEP: read reference/android.md]
    J -- adaptive --> M[NEXT STEP: read both ios.md and android.md]
    E --> N[no extra NEXT STEP]
    C --> N
    I --> N
    J --> O[context-signals: setup.platform]
    O --> P{SKILL.src.md routing}
    P -- ios/android/adaptive --> Q[skip live and detect.mjs]
    P -- web/null --> R[live and detect.mjs available]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[PRODUCT.md loaded] --> B{Platform section?}
    B -- absent --> C[null treated as web]
    B -- present --> D{First non-empty line}
    D -- web --> E[return web]
    D -- ios --> F[return ios]
    D -- android --> G[return android]
    D -- adaptive --> H[return adaptive]
    D -- ios and android --> H
    D -- other --> I[return null, web default]
    F --> J{CLI output}
    G --> J
    H --> J
    J -- ios --> K[NEXT STEP: read reference/ios.md]
    J -- android --> L[NEXT STEP: read reference/android.md]
    J -- adaptive --> M[NEXT STEP: read both ios.md and android.md]
    E --> N[no extra NEXT STEP]
    C --> N
    I --> N
    J --> O[context-signals: setup.platform]
    O --> P{SKILL.src.md routing}
    P -- ios/android/adaptive --> Q[skip live and detect.mjs]
    P -- web/null --> R[live and detect.mjs available]
Loading

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "Add a platform axis (web / ios / android..." | Re-trigger Greptile

Comment thread tests/context.test.mjs
Comment on lines +836 to +861
it('appends a native platform directive for an ios project', async () => {
write('PRODUCT.md', '# Acme\n\n## Register\n\nproduct\n\n## Platform\n\nios\n');
const { spawnSync } = await import('node:child_process');
const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } });
assert.equal(res.status, 0);
assert.match(res.stdout, /This project targets `ios`\./);
assert.match(res.stdout, /read `reference\/ios\.md`/);
});

it('appends both native directives for an adaptive project', async () => {
write('PRODUCT.md', '# Acme\n\n## Register\n\nproduct\n\n## Platform\n\nadaptive\n');
const { spawnSync } = await import('node:child_process');
const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } });
assert.equal(res.status, 0);
assert.match(res.stdout, /targets `adaptive` \(both iOS and Android\)/);
assert.match(res.stdout, /reference\/ios\.md` and `reference\/android\.md`/);
});

it('appends no native platform directive for a web project', async () => {
write('PRODUCT.md', '# Acme\n\n## Register\n\nproduct\n\n## Platform\n\nweb\n');
const { spawnSync } = await import('node:child_process');
const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } });
assert.equal(res.status, 0);
assert.equal(res.stdout.includes('This project targets'), false);
assert.equal(res.stdout.includes('reference/ios.md'), false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing CLI integration test for android platform

The CLI test suite covers ios, adaptive, and web platform directives, but there is no corresponding test for android. The code path for android inside the CLI (nativeRefs = [platform]) is identical to ios, so a regression there (e.g. a typo in the string 'android') would go undetected by integration tests. Adding a fourth case alongside the existing three would close the gap symmetrically.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code

@vinaypokharkar

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all of it addressed. Pushed a rebased, source-first rewrite (ecb8c03d).

Blockers

  • Rebased onto main. The branch is now one commit on top of current main (was 119 behind, bumping to 3.6.0 while main was at 3.8.0). Bumped to 3.9.0. The ~49-file conflicts are gone — the PR shows MERGEABLE. Along the way I reconciled the two upstream refactors my change overlapped: impeccable-paths moving under lib/, and the new resolveProjectRoot export.
  • Source-first. Dropped every generated provider tree and ./plugin. The commit is only skill/, scripts/build.js, tests/, CLAUDE.md, NOTICE.md, the changelog, and the version. The sync workflow regenerates the rest on merge.

Native compresses register

  • Unified both native refs on your framing: earned familiarity is the bar, register's role is narrow on native, platform conformance is the structural/interaction/a11y bar regardless of register, and brand lives in the expressive layer (theming, type scale, color, motion) — never by breaking the rails.
  • Fixed the contradiction: ios.md no longer reads "native is the opposite of brand." It now matches android.md and explicitly cites Calm / Duolingo / Spotify carrying identity inside the platform.

a11y ownership

  • Stripped the ## Accessibility section from both native refs so design-time loading doesn't go timid. audit.md's Platform section owns native a11y (VoiceOver / TalkBack / Dynamic Type / scalable reflow).

Smaller

  • adaptive is now threaded everywhere it was missing: init Step 2, the Step 7 summary, the CLAUDE.md heading, and the adapt / audit / animate / layout divergence notes.
  • Added a skill-behavior scenario (10): PRODUCT.md with ## Platform: ios → the agent loads ios.md, asserted on the tool-call trace, plus a PRODUCT_MD_SAMPLE_IOS fixture and the README baseline row.

Build / tests

  • bun run build is green on prose (now scans skill/ too — fixed two em dashes I'd introduced), skill-prose, and counts. The only remaining failure is the plugin/-subtree version-drift guard (Plugin channel lags skill releases — version files out of sync on main #274), which is expected for a source-first change and resolves once the sync workflow regenerates the subtree on merge.
  • extractPlatform unit coverage passes.

One unrelated note: there are two pre-existing Windows-only test failures in the monorepo --target path tests (productPath compared with / while path.relative yields \ on Windows). Not touched by this PR and green on CI; happy to send a separate fix if useful.

@rfermontero

Copy link
Copy Markdown

Is this stop? May I help here? I wpuld love to use thsi in my native apps

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] React native mobile app support

3 participants