open-index/open-npm huggingface dataset:
npm package: @surfaice/action
Description: GitHub Action for detecting UI drift with Surfaice
Latest version: 0.0.2

# @surfaice/action

GitHub Action for detecting UI drift using Surfaice — automatically catch when your live UI diverges from committed `.surfaice.md` manifests.

## Usage

```yaml
# .github/workflows/ui-drift.yml
name: UI Drift Check

on:
  pull_request:
  push:
    branches: [main]

jobs:
  surfaice:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Start your app (or use a preview URL)
      - name: Start app
        run: pnpm dev &
        
      - name: Wait for app
        run: npx wait-on http://localhost:3000

      - name: Check for UI drift
        uses: surfaiceai/surfaice/packages/action@main
        with:
          base-url: 'http://localhost:3000'
          surfaice-dir: 'surfaice/'
          fail-on-drift: 'true'
```

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| `base-url` | ✅ | — | Base URL of the running app |
| `surfaice-dir` | | `surfaice/` | Directory with `.surfaice.md` files |
| `fail-on-drift` | | `true` | Fail the action if drift is detected |
| `output-format` | | `text` | Output format: `text` or `json` |

## Outputs

| Output | Description |
|--------|-------------|
| `drift-detected` | `"true"` or `"false"` |
| `drift-summary` | Human-readable summary of drift |
| `diff-json` | JSON-encoded diff results |

## Workflow with Surfaice CLI

```yaml
- name: Export UI manifests
  run: npx @surfaice/cli export -u http://localhost:3000 -r /settings /dashboard

- name: Commit if changed
  run: |
    git add surfaice/
    git diff --staged --quiet || git commit -m "chore: update surfaice manifests"
```
npm package: @surfaice/cli
Description: Surfaice CLI — export, check, and diff UI manifests
Latest version: 0.0.2

# @surfaice/cli

CLI tool for managing Surfaice UI manifests — export, check drift, and diff changes.

## Install

```bash
npm install -g @surfaice/cli
# or use without installing:
npx @surfaice/cli
```

## Commands

### `surfaice export`
Fetch routes from a running app and save `.surfaice.md` files.

```bash
surfaice export -u http://localhost:3000 -r /settings /dashboard -o ./surfaice/
```

### `surfaice check`
Compare committed `.surfaice.md` files against the live app. Exit code 1 on drift — CI-ready.

```bash
surfaice check -u http://localhost:3000 -d ./surfaice/
# ✅ /settings — 6 elements match
# ❌ /dashboard — DRIFT: 1 added, 1 changed
```

### `surfaice diff`
Show human-readable or JSON diff between committed and live.

```bash
surfaice diff -u http://localhost:3000 -d ./surfaice/
surfaice diff -u http://localhost:3000 -d ./surfaice/ --json
```

## CI Integration

```yaml
# .github/workflows/surfaice.yml
- name: Check for UI drift
  run: surfaice check -u ${{ env.APP_URL }} -d ./surfaice/
```
npm package: @surfaice/differ
Description: Structural diff engine for Surfaice pages
Latest version: 0.0.2

# @surfaice/differ

Structural diff engine for Surfaice pages — compare two `SurfaicePage` ASTs and detect UI drift.

## Install

```bash
npm install @surfaice/differ @surfaice/format
```

## Usage

```typescript
import { diff } from '@surfaice/differ'
import { parse } from '@surfaice/format'

const expected = parse(committedMarkdown)
const actual = parse(liveMarkdown)

const result = diff(expected, actual)

if (result.status === 'drift') {
  console.log(result.summary)  // "2 added, 1 removed, 3 changed"
  result.added.forEach(el => console.log(`+ [${el.id}] ${el.label}`))
  result.removed.forEach(el => console.log(`- [${el.id}] ${el.label}`))
  result.changed.forEach(c => console.log(`~ [${c.id}].${c.field}: "${c.expected}" → "${c.actual}"`))
}
```

## What Gets Diffed

- **Added** — elements in actual but not in committed
- **Removed** — elements in committed but not in actual
- **Changed** — field-level diffs: `label`, `type`, `action`, `result`, `navigates`, `accepts`, `shows`, `current`, `attributes`, `options`
- **Nested** — elements inside `reveals` are diffed recursively
npm package: @surfaice/format
Description: Surfaice format package
Latest version: 0.0.3

# @surfaice/format

Parser, serializer, and validator for the `.surfaice.md` format — the machine-readable UI description used by [Surfaice](https://github.com/surfaiceai/surfaice).

## Install

```bash
npm install @surfaice/format
```

## Usage

```typescript
import { parse, serialize, validate } from '@surfaice/format'

// Parse a .surfaice.md file
const page = parse(markdownString)  // → SurfaicePage AST

// Validate structure
const errors = validate(page)       // → ValidationError[]

// Serialize back to markdown
const md = serialize(page)          // → string (roundtrip stable)
```

## What Is `.surfaice.md`?

A markdown-based format that describes your UI elements for AI agents and CI tools:

```markdown
---
surfaice: v1
route: /settings
states: [auth-required]
---

# /settings

## Profile
- [name] textbox "Display Name" → current: {user.name} → accepts: string
- [save] button "Save Changes" (destructive) → PUT /api/profile → toast 'Saved!'
```

## API

### `parse(markdown: string): SurfaicePage`
### `serialize(page: SurfaicePage): string`
### `validate(page: SurfaicePage): ValidationError[]`

## Part of Surfaice

- [`@surfaice/react`](../react) — React annotations
- [`@surfaice/differ`](../differ) — Structural diff engine
- [`@surfaice/cli`](../cli) — CLI tools
- [`@surfaice/next`](../next) — Next.js middleware
npm package: @surfaice/next
Description: Next.js middleware for Surfaice — Accept: text/surfaice runtime serving
Latest version: 0.0.4

# @surfaice/next

Next.js middleware and plugin for Surfaice — serve your UI as markdown to AI agents via `Accept: text/surfaice`.

## Install

```bash
npm install @surfaice/next @surfaice/react @surfaice/format
```

## Usage

### 1. Wrap your Next.js config

```typescript
// next.config.ts
import { withSurfaice } from '@surfaice/next'

export default withSurfaice({
  enabled: process.env.SURFAICE_ENABLED === 'true',
  exclude: ['/api', '/_next'],
})(nextConfig)
```

### 2. Add middleware

```typescript
// middleware.ts
import { shouldHandleSurfaice, isRouteAllowed } from '@surfaice/next'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(req: NextRequest) {
  const accept = req.headers.get('accept') ?? ''

  if (shouldHandleSurfaice(accept) && isRouteAllowed(req.nextUrl.pathname, config)) {
    // Rewrite to Surfaice render mode
    const url = req.nextUrl.clone()
    url.searchParams.set('__surfaice', '1')
    return NextResponse.rewrite(url)
  }

  return NextResponse.next()
}
```

### 3. Request in Surfaice format

```bash
curl -H "Accept: text/surfaice" http://localhost:3000/settings
```

## Same URL, Different Content Type

The core principle: your `/settings` page serves HTML to browsers and markdown to agents — same URL, same auth, same data.
npm package: @surfaice/react
Description: React annotations for Surfaice — describe your UI once, agents navigate it
Latest version: 0.0.3

# @surfaice/react

React annotation components for Surfaice — describe your UI once, agents navigate it, CI verifies it.

## Install

```bash
npm install @surfaice/react @surfaice/format
```

## Usage

```tsx
import { SurfaiceProvider, ui } from '@surfaice/react'

// Wrap your app
<SurfaiceProvider enabled={process.env.SURFAICE_ENABLED === 'true'}>
  <App />
</SurfaiceProvider>

// Annotate your pages
<ui.page route="/settings" name="Settings Page" states={['auth-required']}>
  <ui.section name="Profile">
    <ui.element id="name" type="textbox" label="Display Name"
      value={user.name}
      current="{user.name}"
      action="PUT /api/profile">
      <input value={user.name} onChange={handleChange} />
    </ui.element>
    <ui.element id="save" type="button" label="Save Changes"
      action="PUT /api/profile"
      result="toast 'Saved!'">
      <button onClick={handleSave}>Save</button>
    </ui.element>
  </ui.section>
</ui.page>
```

## API

### `<SurfaiceProvider enabled? />`
Context provider. Set `enabled={true}` to collect annotations. Zero overhead when disabled.

### `ui.page` / `ui.section` / `ui.element`
Annotation components — always render children as-is, optionally register metadata.

### `useSurfaicePage(): SurfaicePage | null`
Hook to access the collected page AST. Use in middleware or dev tools.

## Zero Overhead When Disabled

All annotation components are pure passthroughs when `enabled={false}` (the default). Ship to production without worry.
npm package: @surfboard/core
Description: Server component of the surfboard dashboard, providing data sources
Latest version: 0.4.0

# 🌊 Surfboard

![Package version](https://img.shields.io/npm/v/@surfboard/core.svg?style=flat&logo=npm)
![License](https://img.shields.io/github/license/adaedra/surfboard-core.svg)
![Build status](https://img.shields.io/circleci/project/github/adaedra/surfboard-core/master.svg?style=flat&logo=circleci)

Surfboard is a modular dashboard project, based on modern web development tools (JavaScript, React,
Rx).
The aim is to create a tool that adapts to the need of its users and not the other way around, by
providing just the needed functional bricks to build your dashboard but leave all flexibility to
the user.

## `@surfboard/core`

This component is the core server, responsible for gathering data needed by dashboards and
dispatching them to the connected clients. You configure it by giving data sources (in the form of
RxJS observables) and it provides a websocket aggregating and dispatching all data as it is updated
by the sources.

## Warning

This project is still in initial development and is not completely ready yet for prime time. You
can toy with it, but be aware that big changes can arise at any moment while this project hasn't
reached the 1.0 release. Also, documentation and inter-packages dependencies may be a bit wonky
during this time.

## How to

You can see the [surfboard-example](https://github.com/adaedra/surfboard-example) repository for
an example of a surfboard dashboard.

First, add surfboard to your project:

```
npm i --save @surfboard/core
```

Then, create a `surfboard.server.js` file configuring your sources:

```js
const { interval } = require('rxjs')

const sources = {
    tick: interval(1000)
    // ...
}

module.exports = { sources }
```

And simply run the server to have a socket to listen to with the `surfboard` command. It will
create a server on port 1337 on which you can connect a websocket client.
npm package: @surfboard/dev-server
Description: Tweaked webpack-dev-server to load the surfboard server side
Latest version: 0.2.0

# 🌊 Surfboard

![Package version](https://img.shields.io/npm/v/@surfboard/dev-server.svg?style=flat&logo=npm)
![License](https://img.shields.io/github/license/adaedra/surfboard-dev-server.svg)
![Build status](https://img.shields.io/circleci/project/github/adaedra/surfboard-dev-server/master.svg?style=flat&logo=circleci)

Surfboard is a modular dashboard project, based on modern web development tools (JavaScript, React,
Rx).
The aim is to create a tool that adapts to the need of its users and not the other way around, by
providing just the needed functional bricks to build your dashboard but leave all flexibility to
the user.

## `@surfboard/dev-server`

This package just provides a handy command `surfboard-dev-server` to run a webpack dev server
including the surfboard server websocket (please note that changes to server side are not
automatically picked up, only changes to client side which are managed by webpack).

## Warning

This project is still in initial development and is not completely ready yet for prime time. You
can toy with it, but be aware that big changes can arise at any moment while this project hasn't
reached the 1.0 release. Also, documentation and inter-packages dependencies may be a bit wonky
during this time.

## How to

Add it to your project:

```
npm i --save-dev @surfboard/dev-server
```

Run it:

```
npx surfboard-dev-server
```

This will run a listening server on the port 1337. Just access it with your browser.
npm package: @surfboard/sources
Description: Provides ready-to-use observable sources for surfboard projects
Latest version: 0.2.0

# 🌊 Surfboard

![Package version](https://img.shields.io/npm/v/@surfboard/sources.svg?style=flat&logo=npm)
![License](https://img.shields.io/github/license/adaedra/surfboard-sources.svg)
![Build status](https://img.shields.io/circleci/project/github/adaedra/surfboard-sources/master.svg?style=flat&logo=circleci)

Surfboard is a modular dashboard project, based on modern web development tools (JavaScript, React,
Rx).
The aim is to create a tool that adapts to the need of its users and not the other way around, by
providing just the needed functional bricks to build your dashboard but leave all flexibility to
the user.

## `@surfboard/sources`

This package aims at providing some ready to use methods to build sources for surfboard easily.

## Warning

This project is still in initial development and is not completely ready yet for prime time. You
can toy with it, but be aware that big changes can arise at any moment while this project hasn't
reached the 1.0 release. Also, documentation and inter-packages dependencies may be a bit wonky
during this time.

## How to

Sources are functions that can be called to generate data. They have to be coupled with a
scheduler to become observables.

```js
// surfboard.server.js
const { http, schedule } from '@surfboard/sources'

const sources = {
    http: schedule(
        '0 0,30 * * * *', http('https://service.example/data.json', {}, res => res.json())
    )
}

module.exports = { sources }
```

This will create a scheduler that will fetch the content of the given URL and parse the JSON result
every 30 minutes. See the [node-schedule package](https://www.npmjs.com/package/node-schedule) for
more information about accepted rules for the `schedule` method.

The `http` source takes the same arguments as `fetch`, plus an optional transformation function
to get data from the resource. You can also use rxjs's `pipe` to transform the data at will, the
request will be passed as-is if you don't provide a transformer.