# Project Structure

> Directory layout, dependencies and database schema

---

## Directory layout

```
linkenhancer/
├── module.php            ← webtrees entry point
├── autoload.php          ← PSR-4 autoloader
├── cron-jobs.php         ← cronjob self-registration manifest
├── latest-version.txt    ← current version (synced to LinkEnhancerModule.php)
├── composer.json         ← PHP deps (wt-shared-libs, phpstan)
├── package.json          ← JS deps (gulp, rollup, mocha)
├── gulpfile.mjs          ← build tasks
├── phpstan.neon          ← static analysis config
├── phpstan-bootstrap.php ← PSR-4 bootstrap for PHPStan
├── phpstan-baseline.neon ← known issues
├── src/                  ← PHP (PSR-4)
│   ├── LinkEnhancerModule.php
│   ├── LinkEnhancerModuleExt.php
│   ├── LinkEnhancerUtils.php
│   ├── SettingInterface.php
│   ├── CommonMark/       ← CommonMark extensions
│   ├── DataFix/          ← data-fix handlers + dispatcher
│   ├── Factories/        ← CustomMarkdownFactory
│   ├── Http/             ← request handlers
│   ├── Schema/           ← Migrations + SeedHelpTable
│   └── Services/         ← business logic
├── resources/
│   ├── js/               ← JS source (src/) + build output (bundle-*)
│   ├── css/              ← CSS source + build output
│   ├── lang/             ← .pot / .po / .php (translations)
│   ├── views/            ← PHTML views
│   └── img/              ← screenshots
├── cli/                  ← CLI maintenance scripts
├── tests/                ← standalone + smoke tests
└── util/                 ← build/maintenance shell scripts
```

## Dependencies

- **PHP** (<a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/composer.json" target="_blank" rel="noopener"><code>composer.json</code></a>
):
  - `bschwede/wt-shared-libs` (path repo `../wt-shared-libs`) and
  - [`phpstan`](./phpstan) (dev).
  - Conflicts with:
    - `webtrees <2.2` and
    - [`elysch/webtrees-mitalteli-misc-features`](https://github.com/elysch/webtrees-mitalteli-misc-features)
- **JS** (<a href="https://codeberg.org/bschwede/linkenhancer/src/branch/main/package.json" target="_blank" rel="noopener"><code>package.json</code></a>
):
  - runtime dependency [`tiny-markdown-editor`](https://github.com/jefago/tiny-markdown-editor)
  - dev tooling — Gulp, Rollup, PostCSS (autoprefixer, cssnano), Mocha + chai + sinon, jsdom.

## Database schema

The module owns five tables (prefix `le_`), created incrementally by `src/Schema/Migration#.php`. Every migration is additive and idempotent, so the schema converges on each module load.

### `le_route_help_map`

Maps a webtrees route (path / handler / method) to a help entry — the backing store for context-sensitive manual help. Seeded by `SeedHelpTable`.

| Column | Type | Notes |
| --- | --- | --- |
| `id` | int, PK (auto) | |
| `path` | varchar(255) | route path |
| `handler` | varchar(150) | fully-qualified handler class |
| `handler_key` | varchar(150), index | version-neutral key (2.2.6 + 2.3 compatible) |
| `method` | varchar(20) | |
| `extras` | varchar(60) | |
| `subcontext` | varchar(250) | |
| `category` | varchar(60) | standard / custom module / generic … |
| `order` | int, default 10 | |
| `url` | varchar(250) | |
| `updated_at` | timestamp, nullable | |

*Index on `(path, handler)` and on `handler_key`.*

### `le_record_scan`

One row per scanned GEDCOM record, with an MD5 fingerprint of the record text — lets the CLI index diff incrementally.

| Column | Type | Notes |
| --- | --- | --- |
| `file` | int | tree id (part of PK) |
| `xref` | varchar(20) | (part of PK) |
| `rectype` | varchar(15) | GEDCOM tag, e.g. `INDI`, `OBJE` (part of PK) |
| `hash` | varchar(32) | MD5 of the record text |
| `scanned_at` | timestamp, nullable | |

*Primary key `(file, xref, rectype)`.*

### `le_link_index`

One row per (link, target) found in a record — the foundation for the planned Backlink feature.

| Column | Type | Notes |
| --- | --- | --- |
| `id` | int, PK (auto) | |
| `file` | int | tree id |
| `xref` | varchar(20) | |
| `rectype` | varchar(15) | |
| `tag_path` | varchar(255) | where the link sits in the record |
| `link_class` | varchar(10) | xref / ext / pic / classic / other |
| `token` | text | the raw link token |
| `snippet` | text, nullable | display context captured at build time |
| `target_xref` | varchar(20), nullable | XREF link target |
| `target_tree` | varchar(255), nullable | |
| `target_uid` | varchar(255), nullable, index | UID link target (filled by a length heuristic) |

*Indexes on `(file, xref, rectype)`, `target_xref` and `target_uid`.*

### `le_uid_index`

One row per UID tag found in a record (record-level and fact-level). No unique constraint — UIDs are not guaranteed to be unique.

| Column | Type | Notes |
| --- | --- | --- |
| `id` | int, PK (auto) | |
| `file` | int | tree id |
| `xref` | varchar(20) | |
| `uid` | varchar(255), index | verbatim UID value (no normalization) |
| `rectype` | varchar(15) | |
| `tag_path` | varchar(255) | |
| `hash` | varchar(32) | MD5 fingerprint (incremental change detection) |

*Indexes on `uid` and `(file, xref, rectype)`.*

### `le_index_meta`

A single row (`id = 1`) holding the freshness state of **both** the link index and the UID index.

| Column | Type | Notes |
| --- | --- | --- |
| `id` | int, PK, default 1 | single row |
| `last_run` | timestamp, nullable | last complete link-index run |
| `rows` | int, default 0 | link-index row count |
| `uid_last_run` | timestamp, nullable | last complete UID-index run |
| `uid_rows` | int, default 0 | UID-index row count |
