npm package: @recogito/custom-annotation-view-example
Description: An example plugin that adds a new annotation view route
Latest version: 0.0.10

# Example Plugin: Custom Annotation View

A proof of concept for using a plugin to **inject a custom annotation view**, fully replacing the standard Recogito Studio text-/image annotation view. This plugin currently provides:
1. A new Astro page route at `/[lang]/annotate-custom/[contextId]/[documentId]` 
2. A new menu option in the Document Card, which opens the document in the custom view. 

## Overview

### Injected Page Route

The annotation view is injected using [the standard Astro Integrations mechanism](https://github.com/recogito/example-plugin-annotation-view/blob/bd074aa8cb87a7d199c88a97cb39a14efc73ca89/src/index.ts#L29-L34). The page itself is a minimal `.astro` file ([see here](https://github.com/recogito/example-plugin-annotation-view/blob/54e85847294ed8f737b39718f19967448da524fd/src/pages/custom-annotation-view.astro)) that:
- Computes the realtime channel ID (derived from the server-side env variable).
- Instantiates the React entry-point component.

> [!NOTE] 
> The React component is instantiated with `client:only`. This is necessary because OpenSeadragon is not compatible with SSR! Initially, I had trouble with `client:only` failing in Vite (`client:load` was fine):
>
> ```
> The request url [...] is outside of Vite serving allow list
> ```
>
> I got around this [by explicitely allowing the parent directory in the dev app config](https://github.com/recogito/example-plugin-annotation-view/blob/bd074aa8cb87a7d199c88a97cb39a14efc73ca89/.dev/astro.config.mjs#L17-L23). This ajdustment should only be necessary in dev mode, and wouldn't be needed in a Recogito Studio production deployment. But I haven't tested yet!

### Document Menu Extension

The [GoToExampleCustomView](https://github.com/recogito/example-plugin-annotation-view/blob/bd074aa8cb87a7d199c88a97cb39a14efc73ca89/src/extensions/GoToExampleCustomView.tsx) extension adds a new menu entry to the `project:document-actions` extension point, providing a link to the custom route.

<img src="menu-extension-screenshot.png" style="max-width: 600px;" width="600" />

> [!WARNING] 
> Currently, we have no mechanism for disabling or replace the standard **Open document** and **Open document in new tab** entries. This may be worth thinking about for later!

### Custom Annotation View

The annotation view itself is a [a React single page app](https://github.com/recogito/example-plugin-annotation-view/blob/54e85847294ed8f737b39718f19967448da524fd/src/pages/_components/annotation-tei/annotation-tei.tsx). It receives the following input props from the `.astro` page:

- **documentId**
- **contextId** 
- **channelId** for realtime annotation updates

> [!IMPORTANT] 
> Currently, the component strictly expects a TEI document. It will just fail with other content types (plaintext, PDF, image). Later, we should implement separate React SPAs, and conditionally load them from the `.astro` page.)

**Implementation Notes:**

- Backend interaction happens through the SDK. This requires an **.env file with valid credentials** in the `.dev/` folder.
- I added convenience hooks for easier access to document, context and stored file content.
- Similarly, I added a simplified [SupabaseStorageAdapter](https://github.com/recogito/example-plugin-annotation-view/blob/54e85847294ed8f737b39718f19967448da524fd/src/pages/_components/shared/components/SupabaseStorageAdapter.tsx), adapted from the [core codebase](https://github.com/recogito/recogito-client/blob/develop/src/components/SupabasePlugin/SupabasePlugin.ts). This component transparently handles annotation storage and realtime sync.

## Installation

To test this plugin, you'll need access to a live Recogito Studio backend. For most development, I recommended running the plugin through the SDK development app (already included in this repo), though setup is tricky.

### 1. Build the Plugin

First, build the plugin itself:

```sh
cd .dev
npm install
cd ..
npm install
npm run build
```

### 2. Install in a Local Recogito Client

1. Clone a local copy of the [recogito-client](https://github.com/recogito/recogito-client).
2. Make sure a valid `.env` file is configured in your client.
3. Add this plugin as a local `file:` dependendency in `package.json` (modify the path as needed):

```jsonc
"depdendencies": {
  // ...
  "@recogito/custom-annotation-view-example": "file:../example-plugin-annotation-view"
  // ...
}
```
4. Register the plugin in `astro.config.mjs`:

```diff
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import netlify from '@astrojs/netlify';
+ import CustomViewPlugin from '@recogito/custom-annotation-view-example';

export default defineConfig({
  integrations: [
    react(),
+    CustomViewPlugin()
  ],
  output: 'server',
  adapter: netlify(),
  vite: {
    ssr: {
      noExternal: ['clsx', '@phosphor-icons/*', '@radix-ui/*']
    },
    optimizeDeps: {
      esbuildOptions: {
        target: 'esnext'
      }
    }
  }
});
```
5. Start Recogito Studio with `npm start`.
6. To verify installation was successful: open a project, go to **Project Settings** → **Plugins** and check if the plugin is available in the plugin gallery.
7. Navigate to a **TEI document**, open it in the annotation view, and note its **contextID** and **documentId** from the URL (e.g. http://localhost:4321/en/annotate/[contextId]/[document-id])

### 3. Run with the Development App

For active development, it's easier to use the plugin **outside of Recogito Studio**, with the SDK development app (with hot-reloading).

**Requirements:**

- A valid `.env` file in the `.dev/` folder with backend credentials.
- You must be **logged in to Recogito Studio**. The dev app does not currently provide a login mechanism. Therefore the ugly workaround is:
  - Start a local Recogito Client (e.g. at http://localhost:4321) and log in.
  - Stop Recogito, then **run the dev app on the same port**. This reuses the login credentials from your earlier session.

Finally, open a valid context/document manually:

```
http://localhost:4321/en/annotate-custom/[contextId]/[documentId]
```