Documentation

FastAnn is a production-ready UI annotation library. Attach contextual notes, badges, and dialogs to any DOM element — with zero layout impact. Works with Angular, React, Vue, and plain HTML/jQuery.

Installation

Each framework has its own adapter package. Install only what you need:

# Angular 17+
npm install @fastann/angular

# React 18+
npm install @fastann/react

# Vue 3
npm install @fastann/vue

# jQuery 3+ / plain HTML
npm install @fastann/core @fastann/jquery

The Angular, React, and Vue packages already include @fastann/core as a dependency — you do not need to install core separately for those frameworks.

Cell Keys

A cell key (data-ck) is a stable string identifier for each annotatable element. All directives and components set this attribute automatically.

  • Use a path-style format: col:revenue, row-42:margin, kpi-leads
  • Keys must be stable across page loads — use your data's primary key, not a loop index
  • Keys are scoped to domain + route by the backend, so the same key can exist on different pages
<!-- Angular -->
<td ann-cell-key="row-42:revenue">...</td>

<!-- Vue -->
<AnnotationCell :annotation="ann" cell-key="row-42:revenue">...</AnnotationCell>

<!-- React -->
<AnnotationCell annotation={ann} cellKey="row-42:revenue">...</AnnotationCell>

<!-- jQuery / plain HTML -->
<td data-ck="row-42:revenue">...</td>

Annotation Types

TypeBadgeUse case
info● BlueDefinitions, descriptions
rule● Orange-redBusiness rules, constraints
comment● GreenGeneral notes
warning● AmberData quality alerts
formula● PurpleCalculation logic
action● IndigoAction items
task● Dark blueTasks with to_do / in_progress / done status

Permissions

FastAnn uses a granular per-user, per-page permission model. Set permissions on the backend and pass them in the user object via AnnotationAuthService.setCurrentUser() (Angular) or equivalent.

KeyDescription
add_new_dialog_customCreate a new annotation
edit_title / edit_body / edit_tag / edit_typeEdit annotation fields
edit_statusChange task status (to_do / in_progress / done)
edit_assignee / edit_due_dateAssign tasks
set_position_sizeMove / resize dialogs
delete_dialog_customDelete an annotation
view_comment / send_comment / delete_commentComment thread actions
edit_permissionManage page permissions
view_logsView activity history
set_anchorPin dialog anchor position

Angular Guide

Download Example

Angular 17+  Uses Angular Signals, standalone components, and ng-packagr ESM bundles.

1. Import global CSS

Add to styles.scss (or angular.json styles[]):

@import '@fastann/angular/styles/annotation.css';

2. Register providers

In app.config.ts, provide the backend URL, license key, and (optionally) the domain used to scope annotations:

// app.config.ts
import { ApplicationConfig }  from '@angular/core';
import { provideRouter }      from '@angular/router';
import { provideAnimations }  from '@angular/platform-browser/animations';
import { provideHttpClient }  from '@angular/common/http';
import { ANN_API_URL, ANN_LICENSE_KEY, ANN_DOMAIN } from '@fastann/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimations(),
    provideHttpClient(),
    { provide: ANN_API_URL,     useValue: 'https://api.yourapp.com/api' },
    { provide: ANN_LICENSE_KEY, useValue: 'YOUR_PRO_LICENSE_KEY' },
    // Optional — override the domain used to scope annotations.
    // Defaults to window.location.hostname when omitted.
    // { provide: ANN_DOMAIN, useValue: 'yourapp.com' },
  ],
};
TokenRequiredDescription
ANN_API_URLYesBase URL of your FastAnn backend API (no trailing slash).
ANN_LICENSE_KEYProYour Pro license key. Omit or leave blank to run in Free mode.
ANN_DOMAINOptionalPin the domain used to scope and load annotations. Useful for multi-tenant apps or when your app URL differs from the licensed domain. Falls back to window.location.hostname if not provided.

3. Add the stage overlay

Place <app-annotation-stage /> once at the app root. It renders floating dialogs above all page content.

// app.component.ts
import { AnnotationStageComponent } from '@fastann/angular';

@Component({
  standalone: true,
  imports: [RouterOutlet, AnnotationStageComponent],
  template: `
    <main><router-outlet /></main>
    <app-annotation-stage />
  `,
})
export class AppComponent {}

4. Make cells annotatable

Use AnnotationCellDirective on any element. Declare the annotation inline with ann-* attributes, or pass an Annotation object from the service.

// my-page.component.ts
import {
  AnnotationCellDirective,
  AnnotationKeyDirective,
  CustomAnnotationService,
  DotVisiblePipe,
} from '@fastann/angular';

@Component({
  standalone: true,
  imports: [CommonModule, AnnotationCellDirective, AnnotationKeyDirective, DotVisiblePipe],
  templateUrl: './my-page.component.html',
})
export class MyPageComponent {
  constructor(public customService: CustomAnnotationService) {}
}
<!-- Option A: inline system annotation (library auto-generates id & syncs to DB) -->
<th
  ann-cell-key="col:sales"
  ann-type="info"
  ann-source="Finance"
  ann-title="Sales (USD)"
  ann-body="Gross sales, net of refunds, FX-normalised to USD."
  ann-tag="KPI"
  [ann-default-size-w]="300"
  [ann-default-size-h]="160">
  Sales (USD)
</th>

<!-- Option B: pass API annotation object (dynamic rows) -->
<td
  [annCell]="customService.forCell(row.id + ':sales')"
  [ann-cell-key]="row.id + ':sales'">
  {{ "{{" }} row.sales | number {{ "}}" }}
</td>

<!-- Option C: key-only (no pre-declared annotation — users add notes at runtime) -->
<th [annKey]="'th:product'">Product</th>

5. Load annotations from the API

Call loadFromApi() in your page component's ngOnInit:

import { Component, OnInit, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
import { CustomAnnotationService } from '@fastann/angular';

@Component({ ... })
export class MyPageComponent implements OnInit {
  constructor(
    public customService: CustomAnnotationService,
    private router: Router,
    @Inject(DOCUMENT) private doc: Document,
  ) {}

  ngOnInit() {
    this.customService.loadFromApi(
      this.doc.location.hostname,  // ignored when ANN_DOMAIN is provided via DI
      this.router.url,             // e.g. '/sales/dashboard'
    );
  }
}

Tip: When ANN_DOMAIN is injected (see step 2), it takes precedence over the hostname passed to loadFromApi(). You can safely pass document.location.hostname as a fallback — the injected value wins.

6. Set the current user

After login, pass the user to AnnotationAuthService. This controls which permission-gated actions are shown:

import { AnnotationAuthService } from '@fastann/angular';

constructor(private annAuth: AnnotationAuthService) {}

onLoginSuccess(user: any) {
  this.annAuth.setCurrentUser({
    code: user.code,
    name: user.name,
    role: user.role,
    permissions: user.ann_permissions,   // AnnPermissionItem[] from your API
  });
}

7. (Optional) Built-in panel

AnnPanelComponent provides a draggable panel with filter controls, dark mode, language switch, and permission management. Add it once per page:

import { AnnPanelComponent } from '@fastann/angular';
// in imports: [AnnPanelComponent]
<!-- my-page.component.html -->
<ann-panel />
<div class="page-content"> ... </div>

8. Multi-language

Switch UI language at any time. Supported: 'en', 'vi', 'zh', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'it', 'ru', 'tr', 'id', 'th', 'hi', 'ar', 'nl', 'pl'.

import { AnnotationLabelsService } from '@fastann/angular';

constructor(private labels: AnnotationLabelsService) {}

switchLang(lang: string) { this.labels.setLang(lang as any); }

Vite pre-bundling note

When using @angular-devkit/build-angular v17 (Vite dev server), exclude the library from Vite's dep pre-bundling to avoid esbuild conflicts. Add to angular.json under serve > options:

"prebundle": { "exclude": ["@fastann/angular"] }

React Guide

Download Example

React 18+  Uses useSyncExternalStore for reactive state. No context provider required — the core store is a singleton.

Note: The React adapter is currently in preview. Core store integration and all UI components work; backend sync helpers are planned.

1. Import global CSS

// main.tsx or App.tsx
import '@fastann/react/styles';

2. Add the stage overlay

Place <AnnotationStage /> once at the root. It renders the filter toolbar and floating dialogs:

// App.tsx
import { AnnotationStage } from '@fastann/react';
import '@fastann/react/styles';

export default function App() {
  return (
    <>
      <AnnotationStage />
      <YourPageContent />
    </>
  );
}

3. Make cells annotatable

Use AnnotationCell to wrap any element. Pass an annotation object and a stable cellKey:

// SalesTable.tsx
import { AnnotationCell, AnnotationKey } from '@fastann/react';
import type { Annotation } from '@fastann/react';

const revenueAnn: Annotation = {
  id: 'ann-col-revenue',
  type: 'info',
  source: 'Finance',
  title: 'Total Revenue',
  body: 'Gross revenue net of refunds, FX-normalised to USD.',
  tag: 'KPI',
};

export function SalesTable({ rows }) {
  return (
    <table>
      <thead>
        <tr>
          <AnnotationCell as="th" annotation={revenueAnn} cellKey="col:revenue">
            Revenue
          </AnnotationCell>

          {/* Key-only: no pre-declared annotation, users can add notes */}
          <AnnotationKey as="th" annKey="col:product">Product</AnnotationKey>
        </tr>
      </thead>
      <tbody>
        {rows.map(row => (
          <tr key={row.id}>
            <td>{row.product}</td>
            <AnnotationCell as="td" annotation={rowAnn(row)} cellKey={`${row.id}:revenue`}>
              {row.revenue.toLocaleString()}
            </AnnotationCell>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

4. Access the store in custom components

import { useAnnotationStore, useCustomAnnotations } from '@fastann/react';

function MyToolbar() {
  const { mode, setMode, toggleAddMode, addMode, closeAll, toggleDark } = useAnnotationStore();
  return (
    <div>
      <button onClick={() => setMode('all')}>All</button>
      <button onClick={toggleAddMode} className={addMode ? 'active' : ''}>
        + Add
      </button>
      <button onClick={closeAll}>Close all</button>
      <button onClick={toggleDark}>🌙</button>
    </div>
  );
}

Vue 3  Uses composables backed by @fastann/core pub-sub store and Vue ref reactivity. No Pinia or Vuex required.

Note: The Vue adapter is currently in preview. Core store integration and all UI components work; backend sync helpers are planned.

1. Import global CSS

// main.ts
import '@fastann/vue/styles';

2. Add the stage overlay

Place <AnnotationStage /> once, typically in App.vue. It renders the toolbar and floating dialogs:

<!-- App.vue -->
<script setup lang="ts">
import { AnnotationStage } from '@fastann/vue';
</script>

<template>
  <AnnotationStage />
  <RouterView />
</template>

3. Make cells annotatable

Use AnnotationCell and AnnotationKey components. The as prop sets the rendered tag (default: div):

<!-- SalesTable.vue -->
<script setup lang="ts">
import { AnnotationCell, AnnotationKey } from '@fastann/vue';
import type { Annotation } from '@fastann/vue';

const revenueAnn: Annotation = {
  id: 'ann-col-revenue',
  type: 'info',
  source: 'Finance',
  title: 'Total Revenue',
  body: 'Gross revenue net of refunds.',
  tag: 'KPI',
};
</script>

<template>
  <table>
    <thead>
      <tr>
        <AnnotationCell as="th" :annotation="revenueAnn" cell-key="col:revenue">
          Revenue
        </AnnotationCell>

        <!-- Key-only: users can add notes at runtime -->
        <AnnotationKey as="th" ann-key="col:product">Product</AnnotationKey>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in rows" :key="row.id">
        <td>{{ "{{" }} row.product {{ "}}" }}</td>
        <AnnotationCell
          as="td"
          :annotation="rowAnn(row)"
          :cell-key="`${row.id}:revenue`">
          {{ "{{" }} row.revenue.toLocaleString() {{ "}}" }}
        </AnnotationCell>
      </tr>
    </tbody>
  </table>
</template>

4. Access the store via composables

<script setup lang="ts">
import { useAnnotation, useCustomAnnotations } from '@fastann/vue';

const store  = useAnnotation();
const custom = useCustomAnnotations();

// Reactive refs
// store.mode.value, store.addMode.value, store.dialogs.value

function toggleAdd() { store.toggleAddMode(); }
function closeAll()  { store.closeAll();       }
</script>

Svelte Guide

Download Example

Svelte 4/5  Native Svelte components with reactive stores and context-based dependency injection.

1. Configuration

import { setAnnotationContext } from '@fastann/svelte';

// In your root +layout.svelte
setAnnotationContext({
  apiUrl: 'https://api.yoursite.com/api',
  licenseKey: 'YOUR_PRO_LICENSE_KEY'
});

2. Usage

<script>
  import { AnnotationCell, AnnotationKey } from '@fastann/svelte';
  let rows = [...];
</script>

<table>
  <thead>
    <tr>
      <AnnotationCell as="th" cellKey="col:revenue">Revenue</AnnotationCell>
      <AnnotationKey as="th" annKey="col:product">Product</AnnotationKey>
    </tr>
  </thead>
  <tbody>
    {#each rows as row}
      <tr>
        <td>{row.product}</td>
        <AnnotationCell as="td" cellKey="{row.id}:revenue">
          {row.revenue}
        </AnnotationCell>
      </tr>
    {/each}
  </tbody>
</table>

jQuery / Plain HTML Guide

Download Example

jQuery 3+  UMD bundle — works in any page with a <script> tag, no build step required. Also available as an ESM import for bundler-based projects.

Option A — CDN / script tags (no build step)

<!-- 1. jQuery (skip if already loaded) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<!-- 2. FastAnn core (exposes window.AnnCore) -->
<script src="node_modules/@fastann/core/dist/index.umd.cjs"></script>

<!-- 3. FastAnn jQuery adapter (exposes window.AnnJQuery) -->
<script src="node_modules/@fastann/jquery/dist/annotation.umd.js"></script>

<!-- 4. Styles -->
<link rel="stylesheet" href="node_modules/@fastann/jquery/dist/annotation.css">

Option B — ESM / npm

import { init } from '@fastann/jquery';
import '@fastann/jquery/styles';

Initialize

Call init($) once after the DOM is ready. It mounts the toolbar and dialog overlay automatically:

// ESM
import { init } from '@fastann/jquery';
const ann = init($);

// UMD / CDN
const ann = AnnJQuery.init(window.$);

Make elements annotatable

Use ann.cell() on any DOM element, or the jQuery plugin $.fn.annotationCell:

const salesAnn = {
  id:     'ann-col-sales',
  type:   'info',
  source: 'Finance',
  title:  'Sales (USD)',
  body:   'Gross sales net of refunds.',
  tag:    'KPI',
};

// Plain JS API
ann.cell(document.getElementById('th-sales'), salesAnn, 'col:sales');

// jQuery plugin
$('#th-sales').annotationCell(salesAnn, 'col:sales');

// Key-only — no annotation object yet, toolbar add-mode lets users create one
document.querySelectorAll('[data-ck]').forEach(el => {
  ann.cell(el, { id: el.dataset.ck, type: 'comment', source: 'User',
                 title: el.dataset.ck, body: '', tag: '' });
});

Full HTML example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <link rel="stylesheet" href="node_modules/@fastann/jquery/dist/annotation.css">
</head>
<body>
  <table>
    <thead>
      <tr>
        <th id="th-product" data-ck="th:product">Product</th>
        <th id="th-sales"   data-ck="th:sales"  >Sales</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Widget A</td>
        <td id="r1-sales" data-ck="r1:sales">1,200</td>
      </tr>
    </tbody>
  </table>

  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
  <script src="node_modules/@fastann/core/dist/index.umd.cjs"></script>
  <script src="node_modules/@fastann/jquery/dist/annotation.umd.js"></script>
  <script>
    const ann = AnnJQuery.init($);

    $('#th-sales').annotationCell({
      id: 'ann-th-sales', type: 'info', source: 'Finance',
      title: 'Sales (USD)', body: 'Gross sales, net of refunds.', tag: 'KPI',
    }, 'th:sales');

    ann.cell(document.getElementById('r1-sales'), {
      id: 'ann-r1-sales', type: 'comment', source: 'System',
      title: 'Row 1 — Sales', body: '', tag: '',
    }, 'r1:sales');
  </script>
</body>
</html>

Add-mode (create annotations at runtime)

Click the + Add annotation button in the toolbar to enter add-mode. Then click any data-ck element — a prompt dialog collects the title, body, and tag.

To disable add-mode programmatically:

import { annotationStore } from '@fastann/core';
annotationStore.stopAddMode();

.NET Core 8+ Guide

Download Example

Comprehensive middleware for annotation sync, RSA license verification, and entity-framework persistence.

// Program.cs
builder.Services.AddFastAnn(options => {
    options.UseSqlServer(connectionString);
    options.PublicKeyPem = publicKey;
});

app.UseFastAnn();

NodeJS / Express Guide

Download Example

FastAnn provider for Node.js environments. Supports MongoDB and PostgreSQL via Prisma.

import { FastAnnServer } from '@fastann/node-server';

const fastann = new FastAnnServer({
  db: process.env.DATABASE_URL,
  secret: process.env.ANN_SECRET
});

app.use('/api/ann', fastann.middleware());

License Key Format

A Pro license key is structured as:

Base64(UTF8(payloadJson)) . Base64(RSA_PKCS1_SHA256_signature)

Where the payload JSON is:

{ "dom": "yoursite.com", "plan": "Pro", "uid": "a1b2c3d4" }

Frontend Verification

Verify licenses entirely offline — no round-trip to your server:

export async function verifyLicense(licenseKey: string): Promise<boolean> {
  const [payloadB64, sigB64] = licenseKey.split('.');
  if (!payloadB64 || !sigB64) return false;

  const payloadBytes = Uint8Array.from(atob(payloadB64), c => c.charCodeAt(0));
  const sigBytes     = Uint8Array.from(atob(sigB64),     c => c.charCodeAt(0));

  const cryptoKey = await crypto.subtle.importKey(
    'spki', pemToArrayBuffer(PUBLIC_KEY_PEM),
    { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    false, ['verify'],
  );

  const valid = await crypto.subtle.verify(
    'RSASSA-PKCS1-v1_5', cryptoKey, sigBytes, payloadBytes,
  );
  if (!valid) return false;

  const payload = JSON.parse(new TextDecoder().decode(payloadBytes));
  return payload.dom === window.location.hostname.toLowerCase();
}

License Integration

Once you receive your Pro license key from the purchase confirmation email, register it in your project's entry point as shown below. Replace YOUR_PRO_LICENSE_KEY with the actual key string.

Angular app.config.ts / app.module.ts
import { ApplicationConfig } from '@angular/core';
import { ANN_LICENSE_KEY, ANN_DOMAIN } from '@fastann/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    { provide: ANN_LICENSE_KEY,
      useValue: 'YOUR_PRO_LICENSE_KEY' },
    // Must match the domain encoded in your license payload
    { provide: ANN_DOMAIN,
      useValue: 'yourapp.com' },
  ],
};
React main.tsx / index.tsx
import { AnnProvider } from '@fastann/react';

ReactDOM.createRoot(document.getElementById('root')!)
  .render(
    <AnnProvider
      licenseKey="YOUR_PRO_LICENSE_KEY"
    >
      <App />
    </AnnProvider>
  );
Vue 3 main.ts
import { createApp } from 'vue';
import { FastAnn } from '@fastann/vue';
import App from './App.vue';

const app = createApp(App);
app.use(FastAnn, {
  licenseKey: 'YOUR_PRO_LICENSE_KEY',
});
app.mount('#app');
jQuery / HTML main.js / index.html <script>
import { initFastAnn } from '@fastann/jquery';
// or via CDN: <script src="fastann.jquery.min.js"></script>

initFastAnn({
  licenseKey: 'YOUR_PRO_LICENSE_KEY',
});

The license key is validated offline against the public RSA key embedded in the library — no network call is made at runtime. If the key is invalid or the domain does not match, FastAnn will run in Free mode automatically.