ObsiGate 2.83.2 : recherche fédérée — ShaarIt, 5e source de « Rechercher partout » #62

Merged
bruno merged 1 commits from recherche-shaarit into main 2026-11-04 12:23:56 -05:00
15 changed files with 114 additions and 33 deletions
+10 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section > **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.83.1**. > [Unreleased](#unreleased). La dernière version livrée est **2.83.2**.
--- ---
@@ -14,6 +14,15 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
--- ---
## [2.83.2] — 2026-11-04
- **Recherche fédérée — ShaarIt, 5e source de « Rechercher partout »** (demande de Bruno, 2026-11-04 ; contrat dracodev-commons v0.2.1, `contrats/recherche-federee.md`) : `frontend/js/recherche.js` **re-vendangé verbatim** depuis la source canonique de commons (`ui/recherche/`) — ShaarIt devient une source de la page `/recherche` comme les autres (bases prod/dev, onglet, normaliseur : lien profond canonique `{base}shaare/<shorturl>`, contexte « Bookmark · privé · tags », repli recherche native `/?q=`). Le fichier reste strictement identique chez les cinq hôtes.
- **Page `/recherche`** : onglet et section « ShaarIt — bookmarks », `data-base-shaarit` rendu par le jeton `{{URL_SHAARIT}}` (déjà résolu par la route depuis la 2.83.1) ; la CSP `connect-src` de la page ajoute l'origine ShaarIt résolue (`backend/main.py`).
- **CORS de recherche** (`backend/recherche_cors.py`) : origines officielles ShaarIt prod et dev ajoutées aux listes explicites, `shaarit` ajoutée aux clés sœurs résolues — `GET /api/search` d'ObsiGate devient lisible depuis les pages de ShaarIt, et réciproquement.
- Tests : `tests/frontend/recherche.test.mjs` porté à 5 sections (normaliseur ShaarIt inclus) ; `tests/test_recherche_cors.py` — allowlist attendue à 12 origines.
---
## [2.83.1] — 2026-11-03 ## [2.83.1] — 2026-11-03
- **Barre commune (dracodev-commons v0.2.0 vendangée, @70c837a)** : nouvel onglet **ShaarIt** (bookmarks, réécriture Rust de Shaarli) après Kanbante et avant l'onglet Console Spark — `frontend/draco-barre.css` et `frontend/draco-barre.js` remplacés **verbatim** par la v0.2.0 (hachages vérifiés), logo `frontend/img/logos/shaarit.svg` ajouté tel quel ; le fragment porté dans `frontend/index.html` et `frontend/recherche.html` (jeton `{{URL_SHAARIT}}` remplacé par la route `/recherche`). L'URL ShaarIt suit le **même mécanisme** que les autres apps : `backend/draco_config.py` gagne la clé `shaarit` (défaut officiel `https://shaarit.dracodev.net`, variable `DRACO_SHAARIT_URL`, dérivation `-dev` → `shaarit-dev` depuis l'hôte de l'agent), servie dans `window.DRACO_URLS` et réécrite sur l'onglet comme les autres. - **Barre commune (dracodev-commons v0.2.0 vendangée, @70c837a)** : nouvel onglet **ShaarIt** (bookmarks, réécriture Rust de Shaarli) après Kanbante et avant l'onglet Console Spark — `frontend/draco-barre.css` et `frontend/draco-barre.js` remplacés **verbatim** par la v0.2.0 (hachages vérifiés), logo `frontend/img/logos/shaarit.svg` ajouté tel quel ; le fragment porté dans `frontend/index.html` et `frontend/recherche.html` (jeton `{{URL_SHAARIT}}` remplacé par la route `/recherche`). L'URL ShaarIt suit le **même mécanisme** que les autres apps : `backend/draco_config.py` gagne la clé `shaarit` (défaut officiel `https://shaarit.dracodev.net`, variable `DRACO_SHAARIT_URL`, dérivation `-dev` → `shaarit-dev` depuis l'hôte de l'agent), servie dans `window.DRACO_URLS` et réécrite sur l'onglet comme les autres.
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.83.1-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.83.2-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog ## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.83.1). Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.83.2).
--- ---
*Projet : ObsiGate | Version : 2.83.1 | Dernière mise à jour : Septembre 2026* *Projet : ObsiGate | Version : 2.83.2 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.83.1-blue.svg)]() [![Version](https://img.shields.io/badge/Version-2.83.2-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog ## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.83.1). See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.83.2).
--- ---
*Project: ObsiGate | Version: 2.83.1 | Last updated: September 2026* *Project: ObsiGate | Version: 2.83.2 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.83.1 2.83.2
+1 -1
View File
@@ -1010,7 +1010,7 @@ if FRONTEND_DIR.exists():
soeurs = " ".join(sorted({ soeurs = " ".join(sorted({
origin_of(urls.get(cle, "")) or "" origin_of(urls.get(cle, "")) or ""
for cle in ("obsigate", "flowdeck", "tiktak", "kanbante", for cle in ("obsigate", "flowdeck", "tiktak", "kanbante",
"agent") "shaarit", "agent")
} - {""})) } - {""}))
csp = ( csp = (
"default-src 'self'; " "default-src 'self'; "
+3 -1
View File
@@ -43,6 +43,7 @@ PROD_ORIGINS = frozenset({
"https://flowdeck.dracodev.net", "https://flowdeck.dracodev.net",
"https://tiktak.dracodev.net", "https://tiktak.dracodev.net",
"https://kanbante.dracodev.net", "https://kanbante.dracodev.net",
"https://shaarit.dracodev.net",
}) })
DEV_ORIGINS = frozenset({ DEV_ORIGINS = frozenset({
@@ -50,6 +51,7 @@ DEV_ORIGINS = frozenset({
"https://flowdeck-dev.dracodev.net", "https://flowdeck-dev.dracodev.net",
"https://tiktak-dev.dracodev.net", "https://tiktak-dev.dracodev.net",
"https://kanbante-dev.dracodev.net", "https://kanbante-dev.dracodev.net",
"https://shaarit-dev.dracodev.net",
}) })
AGENT_ORIGINS = frozenset({ AGENT_ORIGINS = frozenset({
@@ -59,7 +61,7 @@ AGENT_ORIGINS = frozenset({
# Clés de draco_config.urls_publiques() qui sont des applications sœurs. # Clés de draco_config.urls_publiques() qui sont des applications sœurs.
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante") _SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante", "shaarit")
def is_dev_host(hostname: str | None) -> bool: def is_dev_host(hostname: str | None) -> bool:
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]] [[package]]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.83.1" version = "2.83.2"
dependencies = [ dependencies = [
"chrono", "chrono",
"env_logger", "env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "obsigate-desktop" name = "obsigate-desktop"
version = "2.83.1" version = "2.83.2"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"] authors = ["Bruno Charest"]
edition = "2021" edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate", "productName": "ObsiGate",
"version": "2.83.1", "version": "2.83.2",
"identifier": "com.obsigate.desktop", "identifier": "com.obsigate.desktop",
"build": { "build": {
"frontendDist": "../frontend", "frontendDist": "../frontend",
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap # ObsiGate — Roadmap
> **Version :** 2.83.1 | **Dernière mise à jour :** 2026-11-03 > **Version :** 2.83.2 | **Dernière mise à jour :** 2026-11-04
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées. > vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+39 -9
View File
@@ -2,11 +2,13 @@
* chantier 2+3 : onglets, « Tout voir », focus). * chantier 2+3 : onglets, « Tout voir », focus).
* *
* Vanilla, sans dépendance. Un seul fichier vendangé tel quel par chaque * Vanilla, sans dépendance. Un seul fichier vendangé tel quel par chaque
* hôte (ObsiGate, Flowdeck, TikTak, Kanbante) : la configuration vient * hôte (ObsiGate, Flowdeck, TikTak, Kanbante, ShaarIt) : la configuration
* des attributs data-* de #recherche, rendus côté serveur par l'hôte. * vient des attributs data-* de #recherche, rendus côté serveur par
* l'hôte. Source canonique : dracodev-commons, ui/recherche/
* (contrat : contrats/recherche-federee.md).
* *
* Présentation en onglets : Tout, ObsiGate, Flowdeck, TikTak, Kanbante, * Présentation en onglets : Tout, ObsiGate, Flowdeck, TikTak, Kanbante,
* Console Spark. « Tout » = les 4 sections groupées, chaque liste * ShaarIt, Console Spark. « Tout » = les 5 sections groupées, chaque liste
* tronquée à PER_APP_LIMIT ; un onglet application = tous les résultats * tronquée à PER_APP_LIMIT ; un onglet application = tous les résultats
* de cette application (aucune troncature côté client — la requête * de cette application (aucune troncature côté client — la requête
* ObsiGate part avec limit=FULL_LIMIT, les autres API renvoient leur * ObsiGate part avec limit=FULL_LIMIT, les autres API renvoient leur
@@ -46,6 +48,11 @@
* -> { groups: [{ full_name, rows: [{ issue, column }] }] } * -> { groups: [{ full_name, rows: [{ issue, column }] }] }
* Lien profond : {base}board/<full_name> (la carte n'a pas d'URL propre * Lien profond : {base}board/<full_name> (la carte n'a pas d'URL propre
* adressable — même limite que la page /search native de Kanbante). * adressable — même limite que la page /search native de Kanbante).
* - ShaarIt : GET {base}/api/search?q=
* -> { links: [{ id, title, url, description, tags, shorturl,
* private }] }
* Lien profond (contrat liens-profonds) : {base}shaare/<shorturl>.
* Recherche native adressable : {base}?q=<q> (page d'accueil).
* - Console Spark : GET {base-console}/api/v1/recherche?q=&limite= * - Console Spark : GET {base-console}/api/v1/recherche?q=&limite=
* (agent-commun, authentifié par le cookie SSO draco_session) -> * (agent-commun, authentifié par le cookie SSO draco_session) ->
* { q, domain, limite_par_type, resultats[], par_type, * { q, domain, limite_par_type, resultats[], par_type,
@@ -87,8 +94,9 @@
* Authentification cross-origin : les appels sœurs partent avec * Authentification cross-origin : les appels sœurs partent avec
* credentials:"include". Le navigateur envoie alors les cookies de * credentials:"include". Le navigateur envoie alors les cookies de
* chaque origine, dont le cookie SSO partagé draco_session * chaque origine, dont le cookie SSO partagé draco_session
* (Domain=.dracodev.net), accepté par les 4 API de recherche * (Domain=.dracodev.net), accepté par les 5 API de recherche
* (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68) * (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68,
* ShaarIt via la fédération v0.2.1 de commons)
* et par GET /api/v1/recherche de la console (identity() accepte * et par GET /api/v1/recherche de la console (identity() accepte
* draco_session). Chaque sœur n'autorise en CORS que les origines * draco_session). Chaque sœur n'autorise en CORS que les origines
* sœurs (GET sur l'endpoint de recherche uniquement, pas de "*") ; * sœurs (GET sur l'endpoint de recherche uniquement, pas de "*") ;
@@ -109,12 +117,14 @@
flowdeck: "https://flowdeck.dracodev.net/", flowdeck: "https://flowdeck.dracodev.net/",
tiktak: "https://tiktak.dracodev.net/", tiktak: "https://tiktak.dracodev.net/",
kanbante: "https://kanbante.dracodev.net/", kanbante: "https://kanbante.dracodev.net/",
shaarit: "https://shaarit.dracodev.net/",
}; };
var DEV = { var DEV = {
obsigate: "https://og-dev.dracodev.net/", obsigate: "https://og-dev.dracodev.net/",
flowdeck: "https://flowdeck-dev.dracodev.net/", flowdeck: "https://flowdeck-dev.dracodev.net/",
tiktak: "https://tiktak-dev.dracodev.net/", tiktak: "https://tiktak-dev.dracodev.net/",
kanbante: "https://kanbante-dev.dracodev.net/", kanbante: "https://kanbante-dev.dracodev.net/",
shaarit: "https://shaarit-dev.dracodev.net/",
}; };
var PROD_AGENT = "https://agent.dracodev.net/"; var PROD_AGENT = "https://agent.dracodev.net/";
var DEV_AGENT = "https://agent-dev.dracodev.net/"; var DEV_AGENT = "https://agent-dev.dracodev.net/";
@@ -125,6 +135,7 @@
{ key: "flowdeck", label: "Flowdeck" }, { key: "flowdeck", label: "Flowdeck" },
{ key: "tiktak", label: "TikTak" }, { key: "tiktak", label: "TikTak" },
{ key: "kanbante", label: "Kanbante" }, { key: "kanbante", label: "Kanbante" },
{ key: "shaarit", label: "ShaarIt" },
]; ];
var CONSOLE_APP = { key: "console", label: "Console Spark" }; var CONSOLE_APP = { key: "console", label: "Console Spark" };
var CONSOLE_TYPE_LABELS = { var CONSOLE_TYPE_LABELS = {
@@ -145,7 +156,7 @@
planification: "/?section=planifie", planification: "/?section=planifie",
identite: "/?section=identites", identite: "/?section=identites",
}; };
var TABS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante", "console"]; var TABS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante", "shaarit", "console"];
var COLUMN_LABELS = { var COLUMN_LABELS = {
backlog: "Backlog", backlog: "Backlog",
in_progress: "En cours", in_progress: "En cours",
@@ -227,7 +238,7 @@
return TABS.indexOf(v) !== -1 ? v : "all"; return TABS.indexOf(v) !== -1 ? v : "all";
} }
/* Sections visibles pour un onglet. « Tout » = les 4 sections /* Sections visibles pour un onglet. « Tout » = les 5 sections
* groupées (vue #68) ; la console n'y figure pas (voir en-tête). */ * groupées (vue #68) ; la console n'y figure pas (voir en-tête). */
function visibleKeys(tab) { function visibleKeys(tab) {
tab = parseTab(tab); tab = parseTab(tab);
@@ -256,7 +267,7 @@
* Chargement -> "…", ok/vide -> total ("0" honnête), * Chargement -> "…", ok/vide -> total ("0" honnête),
* erreur/indisponible/inactif -> "" (pas de faux 0). * erreur/indisponible/inactif -> "" (pas de faux 0).
* Tout : "…" tant qu'une section charge, sinon somme des totaux * Tout : "…" tant qu'une section charge, sinon somme des totaux
* connus, "" si les 4 sections sont en erreur/inactives. */ * connus, "" si les 5 sections sont en erreur/inactives. */
function tabCounts(states) { function tabCounts(states) {
states = states || {}; states = states || {};
var out = {}; var out = {};
@@ -367,6 +378,22 @@
}; };
}); });
} }
if (app === "shaarit") {
return cut(data.links || []).map(function (l) {
var tags = (l.tags || []).filter(function (t) { return !!t; });
var ctx = "Bookmark";
if (l.private) ctx += " · privé";
if (tags.length) ctx += " · " + tags.join(", ");
return {
title: l.title || l.url || "Sans titre",
snippet: snippetOf(l.description || ""),
context: ctx,
/* Lien profond canonique (contrats/liens-profonds.md) ;
* sans shorturl, repli sur l'URL du bookmark lui-même. */
url: l.shorturl ? base + "shaare/" + l.shorturl : l.url || null,
};
});
}
return []; return [];
} }
@@ -455,13 +482,16 @@
if (app === "tiktak" || app === "kanbante") { if (app === "tiktak" || app === "kanbante") {
return base + "search?q=" + encodeURIComponent(q || ""); return base + "search?q=" + encodeURIComponent(q || "");
} }
if (app === "shaarit") {
return base + "?q=" + encodeURIComponent(q || "");
}
/* ObsiGate et Flowdeck n'ont pas de page de recherche adressable : /* ObsiGate et Flowdeck n'ont pas de page de recherche adressable :
* leur recherche native vit dans la palette (Ctrl+K) de l'app. */ * leur recherche native vit dans la palette (Ctrl+K) de l'app. */
return base; return base;
} }
function nativeSearchLabel(app) { function nativeSearchLabel(app) {
if (app === "tiktak" || app === "kanbante") { if (app === "tiktak" || app === "kanbante" || app === "shaarit") {
return "Ouvrir la recherche dans " + appLabel(app); return "Ouvrir la recherche dans " + appLabel(app);
} }
return "Ouvrir " + appLabel(app) + " (recherche via la palette Ctrl+K)"; return "Ouvrir " + appLabel(app) + " (recherche via la palette Ctrl+K)";
+8 -2
View File
@@ -132,9 +132,10 @@
data-base-obsigate="{{URL_OBSIGATE}}" data-base-obsigate="{{URL_OBSIGATE}}"
data-base-flowdeck="{{URL_FLOWDECK}}" data-base-flowdeck="{{URL_FLOWDECK}}"
data-base-tiktak="{{URL_TIKTAK}}" data-base-tiktak="{{URL_TIKTAK}}"
data-base-kanbante="{{URL_KANBANTE}}"> data-base-kanbante="{{URL_KANBANTE}}"
data-base-shaarit="{{URL_SHAARIT}}">
<h1>Rechercher partout</h1> <h1>Rechercher partout</h1>
<p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p> <p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante, bookmarks ShaarIt — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p>
<div class="rp-form"> <div class="rp-form">
<input id="rp-q" class="rp-input" type="search" name="q" value="" <input id="rp-q" class="rp-input" type="search" name="q" value=""
placeholder="Rechercher partout…" autocomplete="off" autofocus> placeholder="Rechercher partout…" autocomplete="off" autofocus>
@@ -145,6 +146,7 @@
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="flowdeck">Flowdeck <span class="rp-tab-count"></span></button> <button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="flowdeck">Flowdeck <span class="rp-tab-count"></span></button>
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="tiktak">TikTak <span class="rp-tab-count"></span></button> <button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="tiktak">TikTak <span class="rp-tab-count"></span></button>
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="kanbante">Kanbante <span class="rp-tab-count"></span></button> <button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="kanbante">Kanbante <span class="rp-tab-count"></span></button>
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="shaarit">ShaarIt <span class="rp-tab-count"></span></button>
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="console">Console Spark <span class="rp-tab-count"></span></button> <button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="console">Console Spark <span class="rp-tab-count"></span></button>
</nav> </nav>
<section class="rp-section" data-rp-section="obsigate"> <section class="rp-section" data-rp-section="obsigate">
@@ -163,6 +165,10 @@
<div class="rp-head"><h2>Kanbante — cartes</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div> <div class="rp-head"><h2>Kanbante — cartes</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div> <div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
</section> </section>
<section class="rp-section" data-rp-section="shaarit">
<div class="rp-head"><h2>ShaarIt — bookmarks</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
</section>
<section class="rp-section" data-rp-section="console"> <section class="rp-section" data-rp-section="console">
<div class="rp-head"><h2>Console Spark — discussions</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div> <div class="rp-head"><h2>Console Spark — discussions</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div> <div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "obsigate", "name": "obsigate",
"version": "2.83.1", "version": "2.83.2",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js", "main": "patch.js",
"directories": { "directories": {
+36 -5
View File
@@ -38,6 +38,7 @@ const b = rp.resolveBases({
ok(b.tiktak === "https://tiktak-perso.example/", "explicite prioritaire"); ok(b.tiktak === "https://tiktak-perso.example/", "explicite prioritaire");
ok(b.obsigate === "https://og-dev.dracodev.net/", "dev pour l'hôte courant"); ok(b.obsigate === "https://og-dev.dracodev.net/", "dev pour l'hôte courant");
ok(b.kanbante === "https://kanbante-dev.dracodev.net/", "dev avant href"); ok(b.kanbante === "https://kanbante-dev.dracodev.net/", "dev avant href");
ok(b.shaarit === "https://shaarit-dev.dracodev.net/", "base shaarit dev");
ok( ok(
rp.searchPath("obsigate", "café") === "api/search?q=caf%C3%A9&vault=all&limit=8", rp.searchPath("obsigate", "café") === "api/search?q=caf%C3%A9&vault=all&limit=8",
"path ObsiGate", "path ObsiGate",
@@ -58,21 +59,47 @@ ok(
"https://tiktak-dev.dracodev.net/search?q=q", "https://tiktak-dev.dracodev.net/search?q=q",
"repli TikTak", "repli TikTak",
); );
ok(
rp.nativeSearchUrl("shaarit", "https://shaarit-dev.dracodev.net/", "q") ===
"https://shaarit-dev.dracodev.net/?q=q",
"repli ShaarIt = accueil ?q=",
);
/* normalize ShaarIt (commons v0.2.1) : lien profond shaare/<shorturl>,
* contexte privé + tags, repli sur l'URL du bookmark sans shorturl */
const signets = rp.normalize(
"shaarit",
{ links: [{ title: "Yandex", url: "https://yandex.com", description: "moteur", tags: ["recherche", "web"], shorturl: "xYz9", private: true }] },
"https://shaarit-dev.dracodev.net/",
"recherche",
);
ok(signets[0].url === "https://shaarit-dev.dracodev.net/shaare/xYz9", "lien profond ShaarIt (shaare/<shorturl>)");
ok(signets[0].context === "Bookmark · privé · recherche, web", "contexte ShaarIt (privé + tags)");
ok(signets[0].snippet === "moteur", "extrait ShaarIt = description");
const sansShort = rp.normalize(
"shaarit",
{ links: [{ title: "", url: "https://exemple.fr", tags: [], shorturl: "" }] },
"https://shaarit.dracodev.net/",
"x",
);
ok(sansShort[0].url === "https://exemple.fr" && sansShort[0].title === "https://exemple.fr", "ShaarIt sans shorturl : repli URL + titre");
/* ---- Chantier 2+3 : onglets, compteurs, URL, console (logique pure) ---- */ /* ---- Chantier 2+3 : onglets, compteurs, URL, console (logique pure) ---- */
/* parseTab */ /* parseTab */
ok(rp.parseTab("obsigate") === "obsigate", "onglet valide conservé"); ok(rp.parseTab("obsigate") === "obsigate", "onglet valide conservé");
ok(rp.parseTab("shaarit") === "shaarit", "onglet shaarit valide");
ok(rp.parseTab("console") === "console", "onglet console valide"); ok(rp.parseTab("console") === "console", "onglet console valide");
ok(rp.parseTab("bidon") === "all", "onglet inconnu -> Tout"); ok(rp.parseTab("bidon") === "all", "onglet inconnu -> Tout");
ok(rp.parseTab(undefined) === "all", "onglet absent -> Tout"); ok(rp.parseTab(undefined) === "all", "onglet absent -> Tout");
/* visibleKeys : Tout = les 4 sections groupées, sans la console */ /* visibleKeys : Tout = les 5 sections groupées, sans la console */
ok(JSON.stringify(rp.visibleKeys("all")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante"]), "Tout = 4 sections, pas la console"); ok(JSON.stringify(rp.visibleKeys("all")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante", "shaarit"]), "Tout = 5 sections, pas la console");
ok(JSON.stringify(rp.visibleKeys("tiktak")) === JSON.stringify(["tiktak"]), "onglet app = sa seule section"); ok(JSON.stringify(rp.visibleKeys("tiktak")) === JSON.stringify(["tiktak"]), "onglet app = sa seule section");
ok(JSON.stringify(rp.visibleKeys("shaarit")) === JSON.stringify(["shaarit"]), "onglet shaarit = sa seule section");
ok(JSON.stringify(rp.visibleKeys("console")) === JSON.stringify(["console"]), "onglet console = sa seule section"); ok(JSON.stringify(rp.visibleKeys("console")) === JSON.stringify(["console"]), "onglet console = sa seule section");
ok(JSON.stringify(rp.visibleKeys("bidon")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante"]), "onglet inconnu -> Tout"); ok(JSON.stringify(rp.visibleKeys("bidon")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante", "shaarit"]), "onglet inconnu -> Tout");
/* pageItems : troncature Tout (8), complet dans l'onglet de l'app */ /* pageItems : troncature Tout (8), complet dans l'onglet de l'app */
let douze = Array.from({ length: 12 }, (_, i) => ({ title: "T" + i })); let douze = Array.from({ length: 12 }, (_, i) => ({ title: "T" + i }));
@@ -85,9 +112,11 @@ let c = rp.tabCounts({
flowdeck: { status: "ok", total: 3 }, flowdeck: { status: "ok", total: 3 },
tiktak: { status: "empty", total: 0 }, tiktak: { status: "empty", total: 0 },
kanbante: { status: "error", total: 0 }, kanbante: { status: "error", total: 0 },
shaarit: { status: "ok", total: 2 },
console: { status: "idle", total: 0 }, console: { status: "idle", total: 0 },
}); });
ok(c.obsigate === "…", "compteur … pendant le chargement"); ok(c.obsigate === "…", "compteur … pendant le chargement");
ok(c.shaarit === "2", "compteur shaarit");
ok(c.flowdeck === "3" && c.tiktak === "0", "compteur total, vide honnête à 0"); ok(c.flowdeck === "3" && c.tiktak === "0", "compteur total, vide honnête à 0");
ok(c.kanbante === "" && c.console === "", "erreur/inactif : pas de faux 0"); ok(c.kanbante === "" && c.console === "", "erreur/inactif : pas de faux 0");
ok(c.all === "…", "Tout = … tant qu'une section charge"); ok(c.all === "…", "Tout = … tant qu'une section charge");
@@ -96,18 +125,20 @@ c = rp.tabCounts({
flowdeck: { status: "ok", total: 2 }, flowdeck: { status: "ok", total: 2 },
tiktak: { status: "empty", total: 0 }, tiktak: { status: "empty", total: 0 },
kanbante: { status: "error", total: 0 }, kanbante: { status: "error", total: 0 },
shaarit: { status: "ok", total: 1 },
console: { status: "ok", total: 4 }, console: { status: "ok", total: 4 },
}); });
ok(c.all === "7", "Tout = somme des totaux connus"); ok(c.all === "8", "Tout = somme des totaux connus (5 sections)");
ok(c.console === "4", "compteur console"); ok(c.console === "4", "compteur console");
c = rp.tabCounts({ c = rp.tabCounts({
obsigate: { status: "error", total: 0 }, obsigate: { status: "error", total: 0 },
flowdeck: { status: "error", total: 0 }, flowdeck: { status: "error", total: 0 },
tiktak: { status: "error", total: 0 }, tiktak: { status: "error", total: 0 },
kanbante: { status: "error", total: 0 }, kanbante: { status: "error", total: 0 },
shaarit: { status: "error", total: 0 },
console: { status: "idle", total: 0 }, console: { status: "idle", total: 0 },
}); });
ok(c.all === "", "Tout vide si les 4 sections sont en erreur"); ok(c.all === "", "Tout vide si les 5 sections sont en erreur");
/* buildSearchUrl : état onglet partageable */ /* buildSearchUrl : état onglet partageable */
ok(rp.buildSearchUrl("café", "tiktak") === "?q=caf%C3%A9&tab=tiktak", "URL q+onglet"); ok(rp.buildSearchUrl("café", "tiktak") === "?q=caf%C3%A9&tab=tiktak", "URL q+onglet");
+5 -2
View File
@@ -38,17 +38,20 @@ def test_origin_of():
def test_sister_origins_dev(): def test_sister_origins_dev():
# Correctif 2026-10-28 : 10 origines officielles (8 sœurs + 2 agent), prod ET dev, # Correctif 2026-10-28 : origines officielles prod ET dev, quelle
# quelle que soit la résolution. # que soit la résolution ; ShaarIt ajoutée par la fédération
# v0.2.1 (12 origines : 10 sœurs + 2 agent).
assert sister_origins(RESOLVED_DEV) == { assert sister_origins(RESOLVED_DEV) == {
"https://og.dracodev.net", "https://og.dracodev.net",
"https://flowdeck.dracodev.net", "https://flowdeck.dracodev.net",
"https://tiktak.dracodev.net", "https://tiktak.dracodev.net",
"https://kanbante.dracodev.net", "https://kanbante.dracodev.net",
"https://shaarit.dracodev.net",
"https://og-dev.dracodev.net", "https://og-dev.dracodev.net",
"https://flowdeck-dev.dracodev.net", "https://flowdeck-dev.dracodev.net",
"https://tiktak-dev.dracodev.net", "https://tiktak-dev.dracodev.net",
"https://kanbante-dev.dracodev.net", "https://kanbante-dev.dracodev.net",
"https://shaarit-dev.dracodev.net",
"https://agent.dracodev.net", "https://agent.dracodev.net",
"https://agent-dev.dracodev.net", "https://agent-dev.dracodev.net",
} }