No description
  • PHP 66.9%
  • JavaScript 16.7%
  • CSS 16.2%
  • Shell 0.2%
Find a file
2026-10-03 14:17:47 +02:00
assets initial commit 2026-10-03 14:17:47 +02:00
public initial commit 2026-10-03 14:17:47 +02:00
src initial commit 2026-10-03 14:17:47 +02:00
vegvisir@19077cbd3e initial commit 2026-10-03 14:17:47 +02:00
.env.example.ini initial commit 2026-10-03 14:17:47 +02:00
.gitignore initial commit 2026-10-03 14:17:47 +02:00
.gitmodules initial commit 2026-10-03 14:17:47 +02:00
composer.json initial commit 2026-10-03 14:17:47 +02:00
composer.lock initial commit 2026-10-03 14:17:47 +02:00
install.sh initial commit 2026-10-03 14:17:47 +02:00
README.md initial commit 2026-10-03 14:17:47 +02:00

Contacts

A Vegvisir-based CardDAV address book with per-contact notes. Contacts are mirrored read-only from a CardDAV server with PHP-CardDavClient into a local SQLite cache and displayed as a searchable A–Z directory. The start page lists the contacts you have notes for. Each contact has a notes page for private text notes, which are stored locally. Nothing is ever written back to the CardDAV server.

First Run

Open the application in your browser and navigate to /install first.

The install page checks that the required PHP extensions are loaded, that the configured CardDAV address book responds, and that the SQLite database exists, has the current schema, and is writable. Press the install button on that page to create or update the database.

After installation, open /. The first visit downloads every contact from the server; later visits only fetch what changed.

Local Setup

Install PHP dependencies for the project and for the Vegvisir submodule:

composer install
composer install --working-dir=vegvisir

Copy the example configuration and point it at your address book:

cp .env.example.ini .env.ini
[carddav]
url = ""
username = ""
password = ""

[app]
locale = "en_US"

The web installer at /install is the preferred setup path. For a fresh command-line reset, you can also run:

./install.sh

That script recreates .carddav.db, which permanently deletes all notes and clears the contact cache. Contacts are downloaded again on the next sync.

PHP requirements

PHP 8.4+ with sqlite3, mbstring, dom, xmlreader, xmlwriter, and openssl. The XML and mbstring extensions are required by sabre/vobject and sabre/xml, which PHP-CardDavClient builds on. gd is optional: with it, list avatars use small generated thumbnails; without it, the full photo is used.

Application Pages

  • /install creates or updates the local SQLite database.
  • / is the notes overview. "Active notes" lists contacts with active notes, most recently noted first, with a preview of the latest note. "Past notes" below it lists contacts whose notes are all archived. The header and the bottom of the page link to the full address book.
  • /contacts lists all contacts grouped by initial letter, with search across names, organizations, email addresses, and phone numbers.
  • /notes?id=… lists a contact's notes grouped by day and lets you add new ones (Ctrl/Cmd+Enter saves), archive them, or delete them permanently. Contacts on both lists open here; the header links to the archive and to the contact details.
  • /notes/archive?id=… lists a contact's archived notes, which can be restored to /notes or deleted permanently. Archived notes are not counted in the contact list badge.
  • /contact?id=… shows a contact's phone numbers, email addresses, postal addresses, websites, birthday, related people, note, and categories.

Synchronization

Pages render from the SQLite cache first, then call POST /api/sync in the background, again every minute while the tab is visible, and whenever the tab becomes visible again. Syncs use the WebDAV sync-collection report with the stored sync token, so an unchanged address book costs a single request. When the server reports changes the list is refreshed in place.

A full resync happens on the first run, after a reinstall, and when the configured address book URL changes. Cached cards that no longer exist on the server are pruned during a full resync. If the server cannot be reached, the cached contacts stay available and the header shows the sync error.

  • GET /api/contacts returns the cached directory (thumbnails and note counts) with the sync status.
  • GET /api/notes?contact=… lists a contact's notes, POST /api/notes (contact, body) creates one, and DELETE /api/notes?id=… removes one. Add archived=1 to the GET to list archived notes instead, and use PATCH /api/notes?id=…&archived=1 (or 0) to archive or restore a note.
  • GET /api/sync returns the sync status; POST /api/sync pulls changes from the server.

Data Storage

The application stores data in .carddav.db in the workspace root. Each contact row keeps the raw vCard, denormalized columns for listing and search, and the contact photo as base64 together with its MIME type, byte size, and dimensions. A small base64 thumbnail is stored alongside for list avatars.

Notes live in the notes table (archived notes have archived_at set) and reference contacts.id with a foreign key (ON DELETE CASCADE). Contact ids stay stable across incremental and full syncs because synced cards are upserted by their URI, so notes remain attached. Notes are deleted together with their contact when the card is removed from the server, and when the configured address book URL changes (which rebuilds the cache).