Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.0"
flutter-version: "3.38.5"
channel: stable
cache: true

Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.0"
flutter-version: "3.38.5"
channel: stable
cache: true

Expand Down
40 changes: 40 additions & 0 deletions .github/workflows/otop-static-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: OTOP Static Checks
on:
pull_request:
push:

jobs:
otop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: UI ID audit (non-blocking report)
run: |
if [ -f scripts/otop-uiid-audit.sh ]; then
bash scripts/otop-uiid-audit.sh || true
cat otop.audit.uiids.md || true
else
echo "No UI audit script."
fi

- name: Hardcoded URL audit (block on critical findings)
run: |
if [ -f scripts/otop-env-audit.sh ]; then
bash scripts/otop-env-audit.sh || true
# fail if we find hardcoded localhost or obvious prod domains in src/ (tune as needed)
if rg -n "http://localhost:|https://api\." . -g'!**/node_modules/**' -g'!**/dist/**' -g'!**/build/**' -g'!**/.next/**' -g'!**/.venv/**' ; then
echo "Hardcoded URL found. Move to ENV/proxy."
exit 1
fi
else
echo "No env audit script."
fi
Comment on lines +21 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

ripgrep rg pre-installed GitHub Actions ubuntu-latest runner 2024 2025

💡 Result:

Short answer: GitHub moved ubuntu-latest to Ubuntu 24.04 (rollout Dec 5, 2024–Jan 17, 2025). Whether rg (ripgrep) is present can differ between image versions — GitHub's image manifests don't list every utility — so you should verify at runtime (rg --version) and install it in your workflow if you need it guaranteed (apt install -y ripgrep). [1][2][3]

How to check in a job:

  • run: rg --version

How to install if missing:

  • run: sudo apt-get update && sudo apt-get install -y ripgrep

Sources:

  • GitHub runner-images (image labels / included OS versions). [1]
  • GitHub hosted runners reference (ubuntu-latest behavior). [2]
  • Ubuntu-24.04 rollout announcement / timeline. [3]

References:
[1] actions/runner-images repository — Available Images. (github.com)
[2] GitHub-hosted runners reference (Docs). (docs.github.com)
[3] Issue announcing ubuntu-latest -> Ubuntu 24.04 rollout (Dec 5, 2024 — Jan 17, 2025). (github.com)

Citations:


Add explicit ripgrep installation to ensure workflow reliability.

Ripgrep (rg) is not guaranteed to be pre-installed on GitHub Actions ubuntu-latest runners. The hardcoded URL audit step (line 26) assumes its availability, which will cause the workflow to fail unexpectedly if it's missing.

Add an installation step before the audit:

- name: Install ripgrep
  run: sudo apt-get update && sudo apt-get install -y ripgrep

Alternatively, check for availability and install conditionally:

- name: Ensure ripgrep is available
  run: command -v rg >/dev/null 2>&1 || sudo apt-get install -y ripgrep
🤖 Prompt for AI Agents
.github/workflows/otop-static-checks.yml around lines 21 to 32: the hardcoded
URL audit step assumes ripgrep (rg) is present on the runner which is not
guaranteed and will cause the job to fail; add a step before the audit to
install ripgrep (e.g., run sudo apt-get update && sudo apt-get install -y
ripgrep) or conditionally install it only if missing (e.g., run command -v rg
>/dev/null 2>&1 || sudo apt-get update && sudo apt-get install -y ripgrep) so
the rg command used on line ~26 is available.


- name: OpenAPI lint (block if spec exists and fails)
run: |
if [ -f scripts/otop-openapi-lint.sh ]; then
bash scripts/otop-openapi-lint.sh
else
echo "No OpenAPI lint script."
fi
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,9 @@ firebase login
Bei Fragen: Pull Request mit Frage erstellen oder Issue auf GitHub.

**Owner:** devshift-stack (dsactivi)

## OTOP Rules (MUST)
- Add `data-otop-id` + `data-testid` to every interactive UI component (Web).
- React Native: add `testID` + `accessibilityLabel="otop:<id>"`.
- Do not hardcode API URLs; use ENV/proxy.
- Backend APIs must have OpenAPI with unique `operationId` + structured `tags`.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.widget.RemoteViews
import es.antonborri.home_widget.HomeWidgetProvider
import com.alanko.ai.R

class AlankoWidgetProvider : HomeWidgetProvider() {

Expand Down
107 changes: 107 additions & 0 deletions docs/otop-standard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# OTOP Standard (Repo-weit)

## Ziel
1) **Backend-Funktionen** sind eindeutig & stabil referenzierbar (OpenAPI `operationId`).
2) **UI-Elemente** sind eindeutig & stabil referenzierbar (`data-otop-id` / `data-testid`).
3) **Verbindung** Frontend↔Backend ist robust (keine hardcoded URLs).

---

## 1) Backend Standard (OpenAPI = Function Registry)

### Pflicht
- Jede REST-Operation MUSS besitzen:
- `operationId` (eindeutig innerhalb der Spec)
- `tags` (mind. 1 Tag; dient als Gruppierung im Tool)

### operationId Konvention (deterministisch)
**Format:** `{tagSlug}_{method}_{pathSlug}`

Beispiele:
- Tag: `Tasks`, Methode: `POST`, Pfad: `/tasks`
→ `tasks_post_tasks`
- Tag: `Candidates`, Methode: `GET`, Pfad: `/candidates/{id}`
→ `candidates_get_candidates_id`

**Regeln:**
- `tagSlug` = lower + `_` statt Leerzeichen
- `pathSlug` = path ohne führenden `/`, `/`→`_`, `{id}`→`id`
- Keine Sonderzeichen, nur `[a-z0-9_]`

### Tags Konvention
- Tags sollten “Menü-Struktur” im OTOP Tool abbilden:
- `Auth`, `Agents`, `Tasks`, `CRM`, `Telephony`, `Admin`, `Utils`
- Optional: “Substruktur” im Tag-Name: `CRM/Candidates`, `CRM/Jobs`

### Health/Ready (empfohlen)
- `/health` (liveness)
- `/ready` (readiness; z.B. DB/Queue ready)

---

## 2) Frontend Standard (UI IDs = Link Targets)

### Web (React/Vite/Next)
Jede interaktive Komponente MUSS haben:
- `data-testid`
- `data-otop-id`

**ID Schema:** `{domain}.{entity}.{screen}.{component}.{action}`

Beispiele:
- `crm.candidate.list.search.input`
- `crm.candidate.list.create.button`
- `crm.candidate.form.save.button`
- `agents.task.detail.disable.toggle`

**Beispiel:**
```tsx
<button
data-testid="crm.candidate.list.create.button"
data-otop-id="crm.candidate.list.create.button"
>
Create
</button>
```

### React Native (Expo)
React Native nutzt kein `data-*`, darum:
- `testID` (Tests & Tool-Anker)
- `accessibilityLabel` (OTOP-Label, stabil)

```tsx
<TouchableOpacity
testID="crm.candidate.list.create.button"
accessibilityLabel="otop:crm.candidate.list.create.button"
>
<Text>Create</Text>
</TouchableOpacity>
```

### Flutter
```dart
ElevatedButton(
key: const Key('crm.candidate.list.create.button'),
onPressed: () {},
child: const Text('Create'),
)
```

---

## 3) Verbindung Frontend ↔ Backend (keine hardcoded URLs)
Erlaubt:
- Proxy `/api/*` (best)
- ENV `*_API_BASE_URL` (ok)

Verboten:
- Hardcoded Domains/Ports im Code (`http://localhost:...`, `https://api...`)

---

## 4) Definition of Done (für “Fertigstellung”)
Ein UI gilt als „fertig“, wenn:
- alle benötigten Backend-Funktionen entweder
- **verlinkt** sind (UI-ID → operationId), oder
- bewusst als **deaktiviert** markiert sind
- Delete/Disable/Unlink erzeugt Warnung über Auswirkungen (Dependencies)
21 changes: 21 additions & 0 deletions docs/prompts/01_blueprint_from_openapi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Du bist Produkt+Frontend-Architekt.

INPUT:
- OpenAPI Spec (JSON/YAML) oder OpenAPI URL
- Ziel: UI Blueprint aus OpenAPI ableiten (Screens/Buttons/Felder/States)
- Regeln:
- Gruppiere nach Tags.
- Für jede Entity: List + Detail + Create + Edit + Delete Confirm.
- Aus Request Schemas: required/optional Felder + enums ableiten.
- Jede Action referenziert operationId + method + path.
- Jeder Screen hat Loading/Error/Empty/Success.

OUTPUT FORMAT:
- SECTION: <Tag>
- ENTITY: <Entity>
- SCREEN: <Name>
- PURPOSE:
- UI ELEMENTS:
- BUTTONS (mit UI-ID Schema):
- API LINKS (operationId → method path):
- STATES:
14 changes: 14 additions & 0 deletions docs/prompts/02_v0_prompt.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Du bist v0 Prompt-Writer (React + Tailwind + shadcn/ui).

INPUT:
- UI BLUEPRINT (aus 01)
- Regeln:
- shadcn/ui verwenden (Button, Dialog, Table, Input, Select, Tabs, Toast)
- Jeder interaktive Control bekommt data-otop-id + data-testid
- Delete immer mit Confirm Dialog
- List: Search + Filter + Pagination + Empty/Loading/Error
- Forms: required validation + disabled submit + success toast
- API Calls als Platzhalter: api.<operationId>(params)

OUTPUT:
- Gib nur den fertigen v0 Prompt aus.
17 changes: 17 additions & 0 deletions docs/prompts/03_wiring_plan.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Du bist Frontend-Integrator.

INPUT:
- UI BLUEPRINT
- Generated client: Orval (React Query) oder openapi-fetch oder OpenAPI Generator SDK
- Regeln:
- Verwende ausschließlich generated client/hooks.
- Keine hardcoded URLs.
- Jede Mutation invalidiert betroffene Lists (z.B. create invalidiert list).

OUTPUT:
- SCREEN: <name>
- READS:
- MUTATIONS:
- PARAM MAPPING:
- UI STATES:
- CACHE/INVALIDATION:
15 changes: 15 additions & 0 deletions docs/prompts/04_checklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Du bist QA/Reviewer.

INPUT:
- UI BLUEPRINT
- Regeln:
- Buttons: Create/Edit/Delete/Save/Cancel vorhanden
- Delete Confirm vorhanden
- required validation vorhanden
- Loading/Error/Empty/Success vorhanden
- data-otop-id + data-testid überall
- Buttons referenzieren die richtige operationId

OUTPUT:
- SCREEN: ...
- [ ] ...
91 changes: 91 additions & 0 deletions docs/retrofit-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Retrofit Guide (rückwirkend umstellen)

## Warum rückwirkend in Schritten?
Wenn du „alles auf einmal“ in 22 Repos umstellst, entsteht Chaos (Merge-Konflikte, UI-Brüche). Darum:

1) **Standards + Checks überall einführen** (OTOP Pack)
2) **Baselines/Reports erzeugen** (Audit)
3) **Gezielt nachziehen** (repoweise, screenweise)

---

## Schritt 1: Pack in alle Repos übernehmen
- `bash otop-pack-v1/scripts/otop-install.sh <root-mit-repos>`

Das kopiert:
- `docs/otop-standard.md`
- `docs/prompts/*`
- `rules/spectral-otop.yml`
- `scripts/*`
- `.github/workflows/otop-*.yml` (statische Checks)
- Ergänzt optional `AGENTS.md`

---

## Schritt 2: Baseline pro Repo erzeugen
In jedem Repo:
```bash
bash scripts/otop-scan.sh
bash scripts/otop-uiid-audit.sh
bash scripts/otop-openapi-lint.sh
bash scripts/otop-env-audit.sh
```

Ergebnis:
- `otop.config.json` (Scan)
- `otop.audit.uiids.md` (UI-ID Coverage)
- `otop.audit.env.md` (Hardcoded URL Findings)
- Lint-Output für OpenAPI

---

## Schritt 3: Priorisierung (empfohlen)
Basierend auf deinem aktuellen Report:
- **partner**: Hardcoded URLs → ENV (kritisch, sonst nie sauber deploybar)
- **CRM-activi**: OpenAPI ohne operationId → nicht verlinkbar
- **code-cloud-agents / Optimizecodecloudagents**: OpenAPI+Health ok, aber UI-IDs fehlen komplett

---

## Schritt 4: UI IDs rückwirkend einführen (Web)
### 4.1 Schnellster Hebel: “Design System Wrapper”
Lege zentral Komponenten an, die IDs erzwingen:
- `OButton`, `OInput`, `OSelect`, `OLink`
- Props: `otopId` (string), setzt automatisch beide Attribute

Dann ersetzt du schrittweise:
- `<Button ...>` → `<OButton otopId="..." ...>`

### 4.2 Heuristik für IDs (damit KI konsistent bleibt)
- Domain: `crm|agents|auth|telephony|admin|utils`
- entity: `candidate|job|task|agent|user|...`
- screen: `list|detail|form|settings|...`
- component: `table|search|modal|toolbar|...`
- action: `create|save|delete|edit|open|close|...`

### 4.3 Minimalziel pro Screen
- Primary Buttons (Create/Save/Delete/Cancel)
- Inputs/Selects im Form
- Navigation Tabs/Links

---

## Schritt 5: UI IDs rückwirkend einführen (React Native)
- `testID` + `accessibilityLabel="otop:<id>"`
- Für “Custom Buttons” zentral Wrapper `OButtonRN` bauen, der beide setzt.

---

## Schritt 6: OpenAPI operationId rückwirkend ergänzen
Wenn OpenAPI im Repo liegt:
- Führe `python scripts/otop-add-operationid.py api/openapi.yaml` aus
- Danach `bash scripts/otop-openapi-lint.sh`

---

## Schritt 7: “Stop the bleeding”
Sobald ein Repo angefangen hat:
- CI/PR-Checks aktivieren, die neue UI ohne IDs blocken
- OpenAPI ohne operationId blocken

So wächst die Qualität automatisch.
4 changes: 2 additions & 2 deletions lib/services/gemini_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import 'package:google_generative_ai/google_generative_ai.dart';
import 'user_profile_service.dart';

class GeminiService {
// Free tier API key - 15 requests/minute, 1500/day
static const String _apiKey = 'AIzaSyD5jBRl-Ti0r_uSyx5JW24H3CySQ8RWrS8';
// API key from environment variable (dart-define)
static const String _apiKey = String.fromEnvironment('GEMINI_API_KEY', defaultValue: '');
Comment on lines +7 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard empty GEMINI_API_KEY before init

Because _apiKey now defaults to the empty string, _initModel() no longer catches a missing key (it only checks for 'YOUR_GEMINI_API_KEY'). In environments where --dart-define=GEMINI_API_KEY=... is not set (local dev, CI, or test runs), the service will still initialize the GenerativeModel with an empty key and then every request fails at runtime with API errors instead of the intended “API key not set” fallback. Consider treating an empty key as unset (e.g., check isEmpty) to keep the old safe behavior.

Useful? React with 👍 / 👎.


GenerativeModel? _model;
ChatSession? _chat;
Expand Down
12 changes: 4 additions & 8 deletions linux/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@

#include "generated_plugin_registrant.h"

#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
3 changes: 1 addition & 2 deletions linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
record_linux
url_launcher_linux
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
Loading
Loading