Skip to content

Latest commit

 

History

History
126 lines (100 loc) · 14.8 KB

File metadata and controls

126 lines (100 loc) · 14.8 KB

AGENTS.md — Nextcloud Talk (spreed)

Guidance for AI coding agents in this repo. PHP backend in lib/, Behat integration tests in tests/integration/, unit tests in tests/php/. Vue 3 + TS frontend in src/, tests colocated as *.spec.js/*.spec.ts.

Contribution policy

Comply with the AI Contribution Policy (disclosure, accountability, security, licensing, code quality, autonomous behavior) and Contribution Guidelines (testing, DCO, license headers, conventional commits, translations).

Always:

  • Add an Assisted-by: AGENT_NAME:MODEL_VERSION trailer to every AI-assisted commit.
  • Disclose AI tool use in every PR description.
  • Keep PRs focused on one concern; no unrelated files or incidental refactors.
  • Verify dependencies exist in real package registries.
  • Tell the contributor when an action would violate the policy/guidelines — name the rule and the alternative; never silently proceed.
  • Warn before a PR grows too large (approaching several thousand lines) and suggest a split.
  • Recommend a discussion ticket before complex changes (multiple subsystems, architectural decisions, unclear approach).

Never:

  • Open issues, submit PRs, post review comments, or send security reports autonomously — a human submits every contribution.
  • Add Signed-off-by tags (only the human certifies the DCO).
  • Submit unverified security reports; report verified ones via HackerOne, not GitHub issues.
  • Write PR descriptions, review comments, or issue reports for the contributor — these must be in their own words.
  • Fully automate resolution of good first issue-style issues.
  • Submit unreviewed code — remove dead code, redundant logic, excessive comments, and unrelated changes first.

Baseline & tooling

  • l10n/ is generated from Transifex (fix(l10n): Update translations… commits). Never hand-edit translation files.

Backend: PHP 8.2+, Nextcloud server 35 (appinfo/info.xml). Stay portable across MariaDB, MySQL, PostgreSQL, SQLite, Oracle (migrations, query builder, tests).

  • Setup: composer i. lib/Vendor/ is Mozart-bundled third-party code (cuyz/valinor, firebase/php-jwt) — never edit or analyze it.
  • Checks: composer cs:fix, composer psalm, composer rector:fix, composer lint.
  • OpenAPI: docblocks/psalm types + lib/ResponseDefinitions.php feed composer openapi (also regenerates TS types). Regenerate after adding/removing/renaming a route, changing a controller signature or its @param/@return/psalm-shape, changing a response shape, or adding/removing an HTTP status code. CI fails on a stale spec or src/types/openapi/.

Frontend: Vue 3.5 + TypeScript, built with rspack (no Vite). State: Pinia (src/stores/, target) and legacy Vuex 4 (src/store/, being phased out). UI components from @nextcloud/vue only.

  • Setup: npm ci. Build: npm run build (prod), npm run dev/npm run watch.
  • Checks: npm run lint:fix, npm run stylelint:fix, npm run ts:check.
  • OCS/API types generated via npm run ts:generate (also triggered by composer openapi) into src/types/openapi/ — regenerate, don't hand-edit.
  • No compat mode, mixins, mapGetters/$set/::v-deep — removed in the Vue 2→3 migration; don't reintroduce.

Running tests

  • PHP unit (tests/php/): composer run test:unit. Single file: composer run test:unit -- tests/php/Service/AvatarServiceTest.php; filter: composer run test:unit -- --filter="testMethodName".
  • tests/php/bootstrap.php requires ../../../../lib/base.php: the suite only runs when this repo lives at <nextcloud-server>/apps/spreed. If you cannot run a suite, say so — don't claim it passes. composer lint/composer psalm run standalone.
  • Integration (Behat, tests/integration/): run.sh against a local server, or run-docker.sh.
  • Frontend: npm run test (vitest + @vue/test-utils v2; no jest); single file via npm run test -- <path>.

License headers

Every new file needs an SPDX header. Use AGPL-3.0-or-later, never AGPL-3.0-only:

/**
 * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

Adapt the comment style to the file type; files that can't carry a header (binary assets) go in REUSE.toml. CI enforces REUSE.

Git workflow

  • Do not commit or push unless explicitly asked. Leave changes in the working dir, summarize, and suggest a commit message.
  • Never commit/push to main — use a type/issue-or-noid/short-description branch (e.g. fix/12345/handle-mcu-disconnect), not generated names like agent-xxxx.
  • Follow Conventional Commits with a component scope: feat(chat): …, fix(call): …, fix(api): ….
  • Backports happen via a /backport to stable-X.Y comment on the merged PR — don't cherry-pick manually.

Backend conventions (modern pattern first; avoid the drift)

From a 2026-06 tech-debt analysis. New code uses the modern pattern.

  • DI: constructor injection with property promotion. Don't add \OCP\Server::get() calls — existing ones are debt (the service-locator blocks in lib/Room.php where getName() even writes to the DB; ~42 Server::get() calls instantiating federation proxy controllers, 13 in ChatController).
  • Data access: new persisted entities use QBMapper + SnowflakeAwareEntity in lib/Model/ (templates: ConversationTag, ScheduledMessage). Room/Participant are not Entities — hand-hydrated in Manager::createRoomObject() from columns aliased in lib/Model/SelectHelper.php; adding a column means touching the migration, SelectHelper, Manager hydration, and the constructor in lockstep. Query builder only (no raw SQL outside migrations). Build queries outside loops via createParameter/setParameter; chunk IN () with array_chunk(…, IQueryBuilder::MAX_IN_PARAMETERS) for Oracle and array_merge(...$results) once after the loop (see lib/Model/ThreadMapper.php).
  • Services: reads/lookups in lib/Manager.php; writes/mutations + event dispatch in lib/Service/RoomService.php/ParticipantService.php — keep the split. New services in lib/Service/ named *Service. Lib-root GuestManager, MatterbridgeManager, lib/Chat/ChatManager are legacy naming. Use lib/Federation/, RecordingService, BotService, lib/RoomPresets/ as templates, not 2016-era core.
  • Errors: lookups throw domain exceptions (RoomNotFoundException, ParticipantNotFoundException in lib/Exceptions/). Idempotent setters may return bool (false = no-op), e.g. RoomService::setPermissions(); don't return null/false for "not found".
  • Events: typed only (dispatchTyped()), extending the A-prefixed bases in lib/Events/, with Before*/* pairs for mutations; registered in lib/AppInfo/Application.php. No string events/hooks.
  • Controllers/API: OCS controllers needing room/participant context extend AEnvironmentAwareOCSController (populated by InjectionMiddleware via #[RequireRoom]-style attributes in lib/Middleware/Attribute/); others extend OCSController. PHP attributes only (#[NoAdminRequired], #[PublicPage], #[BruteForceProtection], #[ApiRoute]) — never docblock annotations. Responses are DataResponse with psalm shapes from ResponseDefinitions.php.
  • Config: settings go through the lib/Config.php facade; register new app-config keys in lib/ConfigLexicon.php (the emerging registry).
  • Caching: cache prefixes belong in lib/CachePrefix.php — don't add ad-hoc prefixes (drift: hpb_servers lacks talk/, Capabilities.php uses raw 'talk::').
  • PHP style: strict comparisons, ?Type nullables, match over switch, str_contains/str_starts_with, readonly where applicable, arrow functions, JSON_THROW_ON_ERROR on new json calls. Prefer native backed enums for new value sets (only lib/RoomAttributes.php, lib/RoomPresets/Parameter.php exist today); bitflags (Attendee::PERMISSIONS_*, Participant::FLAG_*) stay int constants.

Frontend conventions (modern pattern first; avoid the drift)

  • Components: new/rewritten SFCs use <script setup lang="ts"> with defineProps<T>()/defineEmits<T>().
  • State: new state in a Pinia setup-style TS store (defineStore('x', () => {…}), see src/stores/actor.ts, token.ts). No new options-style or JS stores. Never add to the Vuex modules (conversationsStore.js, messagesStore.js, participantsStore.js) — migration targets; if forced, keep minimal and flag in the PR. Don't add new Pinia↔Vuex coupling. Instantiate stores lazily inside actions/setup (useXStore()), not at module level. Stores sync through reactivity, not the EventBus.
  • Services/composables: TypeScript with named function exports (convert JS ones when touched). Error split: services throw (bare axios, no try/catch, no UI); stores/composables catch and surface via showError(t('spreed', …)) from @nextcloud/dialogs — don't toast from services or swallow with console.debug. Use types from src/types/index.ts. URLs via generateOcsUrl() with {token} placeholders — never concatenation, never OC.linkTo(). Translations: t/n from @nextcloud/l10n, interpolate via placeholder objects; date/time via src/utils/formattedTime.ts (Intl) — no moment.js.
  • EventBus: src/services/EventBus.ts (typed mitt) bridges the non-Vue signaling/call layer into Vue only — not for component-to-component UI coordination or store-to-store sync; register every new event in the Events type. @nextcloud/event-bus is only for cross-app server events.
  • Dialogs: declarative <NcDialog> in the owning template is the target; spawnDialog() only without template context; NcModal is legacy.
  • Icons/loading: vue-material-design-icons components (not icon-* CSS classes); NcLoadingIcon (not icon-loading* divs).
  • Styling: scoped styles with Nextcloud CSS vars (var(--color-*), defined in apps/theming/css/default.css); hardcoded colors only for brand exceptions. Avoid new :deep() overrides of @nextcloud/vue internals — prefer props/slots or an upstream issue. Spacing/dimensions use the standard vars (calc(x * var(--default-grid-baseline))), no magic numbers. Follow the string-writing rules.
  • Call layer (WebRTC/signaling): src/utils/webrtc/simplewebrtc/, webrtc/models/, signaling.js, EmitterMixin.js are pre-Vue legacy — don't copy these patterns; use ES6 classes + async/await, with src/utils/media/pipeline/ as the template. Don't add new manual model .on()/.off() subscriptions in components — prefer a composable wrapper that cleans up.

Known technical debt (2026-06 analysis)

Roadmap, not new-code patterns. Don't extend these; extract/repair when touching nearby code.

Backend:

  1. \OC_Util::tearDownFS()/setupFS() in lib/Chat/Parser/SystemMessage.php — last private-server-API usage; replace with a public per-user FS API.
  2. Room.php service locators — move name resolution / display-name / last-message loading into Manager/formatter; removes the Room↔RoomService cycle and a getter-with-DB-write.
  3. Enum migration — convert constant groups (Room::TYPE_*, Participant::OWNER/…, Attendee::ACTOR_*) to native backed enums, keeping ->value at OCS/DB boundaries.
  4. Room/Participant hydration — introduce a mapper owning the SelectHelper⇄constructor mapping now spread over three files.
  5. Stale "temporary" code — 15× FIXME Temporary solution for the Talk6 release in lib/Manager.php; Room::OBJECT_TYPE_PHONE_LEGACY (@deprecated) still used in 5 places (Notifier, AvatarService, RoomController, RoomService, RestrictStartingCalls).
  6. God classes — RoomController.php (~3.3k, 63 endpoints), ChatController.php (~2.6k), ParticipantService.php (~2.4k, a SessionService would split out), RoomService.php, Manager.php. Don't grow them.
  7. Copy-paste BackendNotifiers — Signaling/, Recording/, Federation/ share doRequest()+retry+PHPUNIT_RUN boilerplate; extract a base.
  8. MatterbridgeManager — 16-branch elseif in generateConfig(), is_null()/strpos/substr clusters; oldest-style file.
  9. Tests — inverted pyramid (~61 unit files vs huge Behat suite)
  10. Static analysis — psalm level 4 with findUnusedCode="false" and ~200-line baseline; tightening surfaces hidden debt.
  11. json_encode/json_decode — ~164 of 193 calls lack JSON_THROW_ON_ERROR; fix opportunistically.
  12. Loose comparison in BackgroundJob/CheckCertificates.php (== null) and Command/Signaling/VerifyKeys.php (!=).

Frontend:

  1. Vuex→Pinia — 3 modules ~4,200 lines (conversationsStore.js, messagesStore.js, participantsStore.js), bidirectional coupling, duplicated reactions state. Finishing removes the non-atomic fan-out in deleteConversation and ~23 useStore() components.
  2. Deprecated WebRTC APIs — pc.addStream() and addstream/removestream listeners in simplewebrtc/peer.js and e2ee/encryption.js; migrate to addTrack()/ontrack.
  3. Legacy call layer (~4,800 lines) — ES6-classify simplewebrtc/ and webrtc/models/, replace EmitterMixin/WildEmitter with EventTarget, convert signaling.js (~26 promise chains) to async/await. Drops wildemitter/util/mockconsole.
  4. crypto-js→Web Crypto — 8 files use it only for SHA (prepareTemporaryMessage.ts, messagesService.ts, stores/session.ts, store/participantsStore.js, AdminSettings/TurnServer.vue, …); crypto.subtle.digest is async.
  5. hark (unmaintained) — media/pipeline/SpeakingMonitor.js, composables/useDevices.js; replace with native AnalyserNode/AudioWorklet.
  6. Options API components — 141 SFCs; migrate directory-by-directory. Self-contained starts: AdminSettings (15/16), BreakoutRoomsEditor (4/4), Dashboard (3/3).
  7. JS stragglers — 8 services, 7 composables, 4 Pinia stores; convert to TS when touched.
  8. EventBus overreach — UI-coordination events (focus-message, scroll-chat-to-bottom) and store-sync listeners should move to reactivity/provide-inject; signaling fan-out stays.
  9. :deep() overrides — ~174 of @nextcloud/vue internals; audit on each library bump, upstream what's useful.
  10. @matrix-org/olm — deprecated upstream for vodozemac; track for src/utils/e2ee/.
  11. Misc — icon-* CSS classes (~11 files), OC.linkTo() in src/collections.js, cropperjs v1, base64-js in e2ee/encryption.js, vue-material-design-icons (421 imports — consider @mdi/js+NcIconSvgWrapper, low priority).