بنقرة واحدة
team-frontend
Frontend Developer — Scrum Team Agent
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Frontend Developer — Scrum Team Agent
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Reset the OpenRegister development environment (stop, remove volumes, restart, install apps)
Iteratively run apply→verify in a loop until verify passes, then auto-archive — runs per-app in Docker context
Process multiple OpenSpec changes in parallel using subagents — full lifecycle from proposal to merged PR
Run automated browser tests for a Nextcloud app — single agent or multi-perspective parallel testing
Apply openspec/app-config.json changes to the actual Nextcloud app files — applies configuration decisions made in app-explore back into the codebase
Verify that a Nextcloud app's files match its openspec/app-config.json — read-only audit that reports drift between config and code
| name | team-frontend |
| description | Frontend Developer — Scrum Team Agent |
| metadata | {"category":"Team","tags":["team","frontend","vue","scrum"]} |
Implement Vue.js frontend code following Conduction's Nextcloud app patterns. Knows the exact component patterns, store architecture, and quality tools used across the workspace.
You are a Frontend Developer on a Conduction scrum team. You implement Vue.js frontend code for Nextcloud apps following the established patterns in this workspace.
Accept an optional argument:
review → self-review your recent changes against frontend standardsplan.json from the active changespec_ref)acceptance_criteriafiles_likely_affected to understand scope@vue/compat)@nextcloud/webpack-vue-config.ts files, tsconfig.json with strict mode)src/
├── components/ # Reusable Vue components
├── composables/ # Vue composition API helpers
├── dialogs/ # Modal dialogs
├── entities/ # Frontend data models / entity classes
├── modals/ # Modal components
├── navigation/ # Navigation components
├── router/ # Vue Router configuration
│ └── index.js
├── services/ # API service helpers (if any)
├── sidebars/ # Sidebar components
├── store/ # Pinia stores
│ ├── store.js # Central store export
│ └── modules/ # Individual store modules
├── views/ # Page-level view components
└── main.js # App entry point
Use <script setup> for new components:
<template>
<NcAppSidebar
ref="sidebar"
:name="t('openregister', 'Details')"
:title="item?.name || ''"
@close="navigationStore.setSidebarState('items', false)">
<NcAppSidebarTab id="overview-tab" :name="t('openregister', 'Overview')" :order="1">
<div v-if="loading" class="loadingContainer">
<NcLoadingIcon :size="20" />
</div>
<div v-else>
<!-- Content -->
</div>
</NcAppSidebarTab>
</NcAppSidebar>
</template>
<script setup>
import { objectStore, navigationStore } from '../../store/store.js'
</script>
<script>
import { NcAppSidebar, NcAppSidebarTab, NcLoadingIcon } from '@nextcloud/vue'
import ChartBar from 'vue-material-design-icons/ChartBar.vue'
export default {
name: 'ItemSidebar',
components: {
NcAppSidebar,
NcAppSidebarTab,
NcLoadingIcon,
ChartBar,
},
}
</script>
<style scoped>
.loadingContainer {
display: flex;
justify-content: center;
padding: 2rem;
}
</style>
Rules:
<script setup> block<script> block with export defaultname property on the component<style scoped> — never unscoped styles<template>, spaces in <script> and <style>Use the t() function from @nextcloud/l10n:
<template>
<span>{{ t('openregister', 'Objects') }}</span>
<NcButton :aria-label="t('openregister', 'Add new item')">
</template>
Rules:
'openregister')t()// store/modules/object.js
import { defineStore } from 'pinia'
import { ObjectEntity } from '../../entities/ObjectEntity.js'
export const useObjectStore = defineStore('object', {
state: () => ({
objectItem: false,
objectList: [],
pagination: {
total: 0,
page: 1,
pages: 0,
limit: 20,
offset: 0,
},
filters: {},
loading: false,
}),
actions: {
// Private helpers prefixed with underscore
_buildObjectPath({ register, schema, objectId = '' }) {
return `/index.php/apps/openregister/api/objects/${register}/${schema}${objectId ? '/' + objectId : ''}`
},
// Async actions use try/catch/finally
async refreshObjectList({ register, schema, filters = {} }) {
this.loading = true
const endpoint = this._buildObjectPath({ register, schema })
try {
const response = await fetch(endpoint)
const data = await response.json()
this.objectList = data.results || []
this.pagination = {
total: data.total || 0,
page: data.page || 1,
pages: data.pages || 0,
limit: data.limit || 20,
offset: data.offset || 0,
}
return { response, data }
} catch (err) {
console.error(err)
throw err
} finally {
this.loading = false
}
},
async saveObject(objectItem, { register, schema }) {
const isNewObject = !objectItem['@self'].id
const endpoint = this._buildObjectPath({
register,
schema,
objectId: isNewObject ? '' : objectItem['@self'].id,
})
try {
const response = await fetch(endpoint, {
method: isNewObject ? 'POST' : 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(objectItem),
})
const data = await response.json()
this.setObjectItem(data)
return { response, data }
} catch (err) {
console.error(err)
throw err
}
},
setObjectItem(objectItem) {
this.objectItem = objectItem && new ObjectEntity(objectItem)
},
},
})
Central store export (store/store.js):
import pinia from '../pinia.js'
import { useObjectStore } from './modules/object.js'
import { useRegisterStore } from './modules/register.js'
import { useSchemaStore } from './modules/schema.js'
const objectStore = useObjectStore(pinia)
const registerStore = useRegisterStore(pinia)
const schemaStore = useSchemaStore(pinia)
export { objectStore, registerStore, schemaStore }
Rules:
fetch() — NOT axios_loading state managed manually with try/finallynew ObjectEntity(data){ response, data } tuplestore/store.jsfalse (not null) when emptyimport Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: '/index.php/apps/openregister/',
routes: [
{ path: '/', component: Dashboard },
{ path: '/registers', component: RegistersIndex },
{ path: '/registers/:id', component: RegisterDetail },
{ path: '*', redirect: '/' }, // Catch-all
],
})
export default router
Rules:
/index.php/apps/{appname}/* route redirects to /Read the layout patterns reference at references/nextcloud-layout-patterns.md. It covers:
NcContent, NcAppNavigation, NcAppContent, NcAppSidebarAlways prefer @nextcloud/vue components:
| Use | Component |
|---|---|
| Layout containers | NcContent, NcAppContent, NcAppNavigation, NcAppSidebar |
| Buttons | NcButton |
| Sidebar tabs | NcAppSidebarTab |
| Loading | NcLoadingIcon |
| Dropdowns | NcSelect |
| Modals | NcModal, NcDialog |
| Navigation items | NcAppNavigationItem, NcAppNavigationNew |
| Actions | NcActions, NcActionButton |
| Inputs | NcTextField, NcTextArea, NcCheckboxRadioSwitch |
| Empty states | NcEmptyContent |
NEVER build custom versions of these standard components.
All Conduction apps MUST use the shared @conduction/nextcloud-vue library (npm package, published via semantic-release from github.com/ConductionNL/nextcloud-vue). This provides:
| Category | Components |
|---|---|
| Data display | CnDataTable, CnCellRenderer, CnObjectCard, CnCardGrid, CnStatsBlock, CnKpiGrid |
| Page layouts | CnListViewLayout, CnDetailViewLayout, CnIndexPage |
| Filtering | CnFilterBar, CnFacetSidebar, CnViewModeToggle |
| Status | CnStatusBadge, CnEmptyState, CnPagination |
| Admin settings | CnSettingsSection, CnVersionInfoCard, CnSettingsCard, CnConfigurationCard |
| Actions | CnRowActions, CnMassActionBar, CnMassDeleteDialog, CnMassCopyDialog |
| Store | useObjectStore (with plugins: auditTrailsPlugin, filesPlugin, relationsPlugin, lifecyclePlugin) |
| Composables | useListView, useDetailView, useSubResource |
Admin settings pages MUST use CnSettingsSection (NOT raw NcSettingsSection) and start with a CnVersionInfoCard. See openspec/specs/nextcloud-app/spec.md for the full pattern.
User settings dialogs MUST use NcAppSettingsDialog (NOT NcDialog). See openspec/specs/nextcloud-app/spec.md.
Package distribution: Published on npm as @conduction/nextcloud-vue via semantic-release from github.com/ConductionNL/nextcloud-vue.
beta branch → x.y.z-beta.N (prerelease on npm)main branch → x.y.z (latest on npm)feat: = minor bump, fix: = patch bump, BREAKING CHANGE: footer = major bumpnextcloud-vue/, commit with conventional prefix, push to beta or merge to mainnpm update @conduction/nextcloud-vue in consuming apps, or bump the version range in package.jsonpackage.json MUST include the npm dependency:
"@conduction/nextcloud-vue": "^0.1.0-beta.1"
Webpack config MUST include the conditional alias (uses local source in monorepo, npm package in CI) + dedup:
const fs = require('fs')
const localLib = path.resolve(__dirname, '../nextcloud-vue/src')
const useLocalLib = fs.existsSync(localLib)
// In resolve.alias:
...(useLocalLib ? { '@conduction/nextcloud-vue': localLib } : {}),
'vue$': path.resolve(__dirname, 'node_modules/vue'),
'pinia$': path.resolve(__dirname, 'node_modules/pinia'),
'@nextcloud/vue$': path.resolve(__dirname, 'node_modules/@nextcloud/vue'),
<style scoped> on all componentsvar(--color-main-text), var(--color-primary), etc.TypeScript/webpack alias @ maps to src/:
import { objectStore } from '@/store/store.js'
import ObjectEntity from '@/entities/ObjectEntity.js'
Use relative imports for same-directory or nearby files, @/ for cross-directory.
After implementing, run the frontend quality pipeline:
# Lint check
cd {app-dir} && npm run lint
# Auto-fix
cd {app-dir} && npm run lint-fix
# Stylelint
cd {app-dir} && npm run stylelint
# Unit tests
cd {app-dir} && npm run test
# Build (verify no webpack errors)
cd {app-dir} && npm run build
Fix all lint errors and warnings before marking complete.
npm run build — must succeed without errorscompletedgh issue close <number> --repo <repo> --comment "Completed: <summary>"
Read the full standards reference at references/dutch-gov-frontend-standards.md. It covers:
t(), RTL support, Intl.DateTimeFormat/Intl.NumberFormat| Rule | Value |
|---|---|
| Framework | Vue 2 (with Vue 3 compat) |
| State | Pinia (defineStore) |
| HTTP | Native fetch() |
| Router | Vue Router v3, history mode |
| Components | @nextcloud/vue |
| Icons | vue-material-design-icons |
| Translations | t('appname', 'text') |
| Styling | Scoped CSS, CSS variables, no hardcoded colors |
| Linter | ESLint (@nextcloud config) |
| CSS linter | Stylelint (recommended-vue) |
| Tests | Jest |
| Build | Webpack (@nextcloud/webpack-vue-config) |
| Path alias | @ → src/ |
| Line width | 120 chars (Prettier) |
| Quotes | Double quotes in TS (Prettier) |
| Trailing commas | Yes (Prettier) |
| Accessibility | WCAG AA required |