Skip to content

Conversation

@DaxServer
Copy link
Owner

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Mapillary workflow: load sequences by ID, select images, batch-edit metadata (language, description, categories), preview wikitext/SDC, and upload.
    • Introduced grid and list views with selection indicators and counters.
    • Added Wikidata rendering for properties, statements, and values.
    • App now uses tabs; new Mapillary tab alongside Bots.
  • Refactor

    • Migrated UI to Naive UI; redesigned bots data table, header, and footer.
    • Improved async handling for authentication and data refresh.
  • Chores

    • Updated tooling, linting, formatting, and Tailwind/PostCSS integration.

Walkthrough

Adds a Mapillary sequence viewing/editing feature (types, store, composable, many components), switches UI from PrimeVue to Naive UI, replaces Prettier JSON with typed config, consolidates ESLint, updates TS/Vite/package configs, and expands auto-imports and global/component type declarations.

Changes

Cohort / File(s) Summary
Mapillary core (types, store, composable)
src/types/mapillary.ts, src/stores/mapillary.store.ts, src/composables/useMapillary.ts
New Mapillary types, Pinia store (sequence state, actions, getters), and composable for validation, loading sequences/SDC, generating wikitext, and submitting uploads.
Mapillary UI components
src/components/mapillary/*
Adds SequenceForm/Select/Edit/Preview/Upload, ListView/GridView, ImageItem, BatchPanel, Inputs, SelectedMessagePanel, languageOptions and wiring to the store/composable.
Wikidata UI & helpers
src/components/wikidata/*, src/types/wikidata.ts, src/components/wikidata/useWikidata.ts
New ValueRenderer, VR, PropertyLabel, StatementItem, StatementsList and typed Wikidata datamodel + small lookup helper.
App structure & pages
src/App.vue, src/main.ts
Tabbed UI (Bots / Mapillary); removed PrimeVue plugin/bootstrap and adjusted provider usage.
Bots area & small components
src/components/BotsTable.vue, src/components/BotsDataTable.vue, src/components/BotActions.vue, src/components/JobStatusTag.vue
Replace PrimeVue components with Naive UI equivalents, convert some render logic to programmatic columns, make mounted hooks async, and adapt templates/slots.
Header / Footer
src/components/Header.vue, src/components/Footer.vue
Header awaits auth check and uses Naive UI layout/button; Footer switched to n-layout-footer and tightened anchor attributes.
Global typings / auto-imports
.eslintrc-auto-import.json, auto-imports.d.ts, components.d.ts, env.d.ts
Removed Aura/PrimeVue globals; added Mapillary, Wikidata, Naive UI components/types, icon globals, and language/useMapillary/useWikidata auto-imports and augmentations.
Tooling / formatting / lint
eslint.config.ts, prettier.config.ts, .prettierrc.json
Consolidated ESLint via defineConfig, added ignores/globals/rule changes; replaced Prettier JSON with typed prettier.config.ts (organize-imports plugin). Deleted old .prettierrc.json.
Build / package / config
package.json, tsconfig.app.json, vite.config.ts, postcss.config.ts
Switched dependencies from PrimeVue → Naive UI, added Bun/tsconfig and Tailwind PostCSS integration, updated tsconfig strictness/paths/types, added dev proxy for /api/mapillary and callback rewrite, and added postcss config.
Styling
src/assets/main.css
Removed primeui-tailwind import; added global .row-selected-global td rule via @apply.
Tests / small edits
src/composables/useBotStatus.spec.ts, components typings edits
Minor import reorder and formatting-only changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as MapillaryTable / SequenceForm
  participant Composable as useMapillary
  participant Store as useMapillaryStore
  participant API as /api/mapillary

  User->>UI: submit(sequenceId)
  UI->>Composable: loadSequence(sequenceId)
  Composable->>Store: reset(), setLoading(true), clearError()
  Composable->>Composable: validateSequenceId()
  alt invalid
    Composable->>Store: setError(...)
    Composable->>Store: setLoading(false)
  else valid
    Composable->>API: GET /sequences/:id
    API-->>Composable: 200 + images
    Composable->>Store: set items, creator, stepper='2'
    Composable->>Store: setLoading(false)
  end
  Note over UI,Store: User interacts (select, edit meta)
  UI->>Store: updateItem(...)/set global inputs
  User->>UI: preview / submit upload
  UI->>Composable: loadSDC(selectedIds) / submitUpload(payload)
  Composable->>API: POST /upload or GET /sdc
  API-->>Composable: response / error
  Composable->>Store: update results or setError(...)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive There is no description provided by the author beyond a placeholder note, so there is no contextual information to verify against the changeset. Please add a concise pull request description that outlines the purpose, key changes, and any relevant context or usage instructions for the new Mapillary sequence uploader feature.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly summarizes the primary change by indicating the addition of the Mapillary sequence uploader feature and marks it as part one of the implementation, making the main intent immediately clear to reviewers.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mapillary-1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (25)
src/types/mapillary.ts (5)

3-3: Clarify captured_at units (ms vs s).

If this mirrors Mapillary Graph API, captured_at is milliseconds since epoch. Encode that in docs or with a branded type to prevent unit bugs.

Example:

-export interface MapillaryImage {
-  captured_at: number
+export interface MapillaryImage {
+  /** Unix timestamp in milliseconds */
+  captured_at: number

Optionally:

type UnixTimeMs = number & { __unit: 'ms' }
captured_at: UnixTimeMs

9-11: Make coordinates self-describing.

Use a named tuple for readability and to prevent lat/lon swaps.

-    coordinates: [number, number] // [longitude, latitude]
+    coordinates: [lon: number, lat: number] // [longitude, latitude]

14-16: Consider adding smaller thumbs or documenting availability.

If consumers may ask for 256/512 sizes, either include optional fields or note that only 1024/original are guaranteed here.


31-36: categories should likely be string[].

A single string limits multi-category selection and complicates UI. Switch to string[] (or union with string for backward compat).

-export interface Metadata {
+export interface Metadata {
   title: string
   description: Description
-  categories: string
+  categories: string[]
   selected: boolean
 }

If existing callers expect string, consider:

categories: string | string[]

40-45: Duplicate id increases drift risk.

MapillaryItem.id duplicates image.id. Prefer deriving from image.id or document that they must match.

-export interface MapillaryItem {
-  image: MapillaryImage
-  meta: Metadata
-  index: number
-  id: string
-}
+export interface MapillaryItem {
+  image: MapillaryImage
+  meta: Metadata
+  index: number
+  /** mirrors image.id */
+  id: string
+}

Or drop id and use image.id at call sites.

prettier.config.ts (2)

19-32: Overrides likely redundant; confirm necessity.

Prettier auto-selects parsers from extensions. Keep overrides only if you observed mis-parsing or plugin ordering issues.


16-16: htmlWhitespaceSensitivity: 'ignore' can alter Vue template spacing.

This broad setting may interact with inline elements (e.g., your JobStatusTag seconds). Consider 'css' (default) unless you need global ignore.

src/components/mapillary/languageOptions.ts (1)

1-4: Add const assertion for immutability.

The languageOptions array would benefit from a as const assertion to make it readonly and provide stricter typing. This prevents accidental mutations and gives better type inference.

-export const languageOptions = [
-  { label: 'English', value: 'en' },
-  { label: 'Deutsch', value: 'de' },
-]
+export const languageOptions = [
+  { label: 'English', value: 'en' },
+  { label: 'Deutsch', value: 'de' },
+] as const
src/components/mapillary/MapillaryBatchPanel.vue (1)

12-14: Consider removing explicit type assertions if events are properly typed.

The update handlers use explicit as string type assertions. If MapillaryInputs properly types its emitted events, these assertions may be unnecessary and could mask type mismatches. Verify that the event payloads are correctly typed in the component definition.

Alternatively, if MapillaryInputs supports v-model, consider using v-model:language, v-model:description, and v-model:categories for cleaner two-way binding.

src/components/mapillary/MapillarySelectedMessagePanel.vue (1)

19-19: Consider using a constant for layout values.

The layout value 'list' is a magic string. If the store defines layout as a union type or enum, consider extracting layout values as constants to prevent typos and improve type safety.

src/stores/mapillary.store.ts (2)

6-6: Consider externalizing the default sequence ID.

The hardcoded default sequence ID 'tulzukst7vufhdo1e4z60f' appears to be test/example data. Consider moving this to an environment variable or configuration file for easier management.

Apply this approach by creating a config file or using an env variable:

// In a config file or using import.meta.env
const DEFAULT_SEQUENCE_ID = import.meta.env.VITE_DEFAULT_MAPILLARY_SEQUENCE_ID || ''

Then use it in the store:

-  const sequenceId = ref<string>('tulzukst7vufhdo1e4z60f')
+  const sequenceId = ref<string>(DEFAULT_SEQUENCE_ID)

11-14: Layout option labels could be more descriptive.

The layout option names "Select" and "Edit" might be clearer as "Grid View" and "List View" to better communicate their purpose to users.

 const layoutOptions = ref<{ name: string; value: Layout }[]>([
-  { name: 'Select', value: 'grid' },
-  { name: 'Edit', value: 'list' },
+  { name: 'Grid View', value: 'grid' },
+  { name: 'List View', value: 'list' },
 ])
src/components/mapillary/MapillarySequenceInfo.vue (1)

36-42: Type assertion may be unnecessary.

The type assertion as Layout on line 41 might be redundant if store.layout is already properly typed. TypeScript should infer the type from the store.

Verify if the type assertion can be removed:

-@update:model-value="(v) => (store.layout = v as Layout)"
+@update:model-value="(v) => (store.layout = v)"

If TypeScript complains, the current code is fine.

src/components/mapillary/MapillaryGridView.vue (2)

8-8: Consider responsive grid layout.

The grid is fixed at 3 columns (grid-cols-3), which may not work well on smaller screens or very large displays. Consider using responsive Tailwind classes.

-<div class="grid grid-cols-3 gap-4 my-5">
+<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 my-5">

24-26: Complex CSS for triangle overlay.

The triangle overlay uses border tricks which work but are not immediately obvious. Consider adding a comment to explain the technique.

+<!-- Triangle overlay using border trick for selected indicator -->
 <div
   class="w-0 h-0 border-t-[66px] border-r-[66px] border-t-green-500 border-r-transparent opacity-80"
 ></div>
src/composables/useMapillary.ts (4)

4-13: DRY the regex: extract a single SEQ_ID_RE and reuse.

Pattern is duplicated here and in validateSequenceId. Extract once to avoid drift.

Apply:

 export const useMapillary = () => {
   const store = useMapillaryStore()

+  const SEQ_ID_RE = /^[a-zA-Z0-9_-]+$/
   // Validation rules (stateless)
   const validationRules = {
     sequenceId: [
       { required: true, message: 'Please enter a sequence ID' },
       {
-        pattern: /^[a-zA-Z0-9_-]+$/,
+        pattern: SEQ_ID_RE,
         message: 'Sequence ID can only contain letters, numbers, underscores, and hyphens',
       },
     ],
   }

47-63: Avoid per-item pushes; map once and assign for fewer reactive updates.

Also, top-level id duplicates image.id; keep one source of truth.

Apply:

-      items.forEach((image, index) => {
-        const date = new Date(image.captured_at).toISOString().split('T')[0]
-        store.items.push({
-          id: image.id,
-          index: index + 1,
-          image,
-          meta: {
-            title: `Photo from Mapillary ${date} (${image.id}).jpg`,
-            description: {
-              language: 'en',
-              text: '',
-            },
-            categories: '',
-            selected: false,
-          },
-        })
-      })
+      const mapped = images.map((image, index) => {
+        const date = new Date(toMs(image.captured_at)).toISOString().split('T')[0]
+        return {
+          // index is useful for UI, id comes from image.id (avoid duplicate top-level id if not needed)
+          index: index + 1,
+          image,
+          meta: {
+            title: `Photo from Mapillary ${date} (${image.id}).jpg`,
+            description: { language: 'en', text: '' },
+            categories: '',
+            selected: false,
+          },
+        }
+      })
+      store.items = mapped

65-69: Creator assignment: use the already-sorted first image and guard both fields.

Small clarity tweak; no behavior change.

-      if (items[0]?.creator?.username) {
-        store.creatorUsername = items[0].creator.username
-        store.creatorId = items[0].creator.id
-      }
+      const first = images[0]
+      if (first?.creator?.username && first?.creator?.id) {
+        store.creatorUsername = first.creator.username
+        store.creatorId = first.creator.id
+      }

32-42: Consider request cancellation to avoid race conditions.

Rapid calls to loadSequence can interleave; last response should win. Use AbortController or a request token.

If useful, I can provide an AbortController-based patch.

src/components/mapillary/MapillaryTable.vue (2)

10-17: Clear error in store when Message is dismissed.

Currently closing the UI message leaves store.error set.

-    <Message
+    <Message
       v-if="store.error"
       severity="error"
       class="mb-4"
       :closable="true"
+      @close="store.error = null"
     >
       {{ store.error }}
     </Message>

49-66: Optional: provide stable keys for list/grid items.

If MapillaryListView/MapillaryGridView iterate with v-for, ensure they use a stable key like image.id to avoid DOM churn.

src/components/mapillary/MapillaryInputs.vue (1)

43-51: Minor a11y: associate labels with controls

Add for/id pairs so labels are clickable and announced.

-<label class="block font-medium text-gray-600">Language</label>
+<label for="lang" class="block font-medium text-gray-600">Language</label>
 <Select
+  input-id="lang"
 ...
 />
-<label class="block font-medium text-gray-600">Description</label>
+<label for="desc" class="block font-medium text-gray-600">Description</label>
 <Textarea
+  id="desc"
 ...
 />
-<label class="block text-sm font-medium text-gray-600">Categories</label>
+<label for="cats" class="block text-sm font-medium text-gray-600">Categories</label>
 <Textarea
+  id="cats"
 ...
 />

Also applies to: 56-64

src/components/mapillary/MapillaryImageItem.vue (1)

10-12: Tailwind class likely invalid: border-l-10

Tailwind doesn’t ship a border-l-10 utility by default. Use arbitrary value or a supported scale.

- class="grid grid-cols-[auto_1fr] gap-4 p-4 border-l-10 border-transparent"
+ class="grid grid-cols-[auto_1fr] gap-4 p-4 border-l-[10px] border-transparent"
eslint.config.ts (2)

25-26: Avoid Prettier/ESLint disagreement on commas

If Prettier’s trailingComma differs, you’ll fight the formatter. Either align Prettier’s trailingComma to always-multiline or drop this rule and let Prettier decide.

-      'comma-dangle': ['error', 'always-multiline'],
+      // Prefer deferring to Prettier; or ensure prettier.config sets trailingComma: 'all'
+      // 'comma-dangle': ['error', 'always-multiline'],

Also applies to: 47-54


12-20: Ignore patterns: reconsider '*.json'

If you want ESLint to validate JSON config (e.g., .eslintrc-auto-import.json), don’t ignore all JSON. Limit the ignore to large data blobs if needed.

-      '*.json',
+      // '*.json', // keep if JSON linting not desired
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cefe82e and 03225ea.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • .eslintrc-auto-import.json (3 hunks)
  • .prettierrc.json (0 hunks)
  • auto-imports.d.ts (6 hunks)
  • components.d.ts (1 hunks)
  • env.d.ts (1 hunks)
  • eslint.config.ts (1 hunks)
  • package.json (2 hunks)
  • prettier.config.ts (1 hunks)
  • src/App.vue (1 hunks)
  • src/components/BotActions.vue (2 hunks)
  • src/components/BotsDataTable.vue (2 hunks)
  • src/components/BotsTable.vue (2 hunks)
  • src/components/Footer.vue (1 hunks)
  • src/components/Header.vue (1 hunks)
  • src/components/JobStatusTag.vue (1 hunks)
  • src/components/mapillary/MapillaryBatchPanel.vue (1 hunks)
  • src/components/mapillary/MapillaryGridView.vue (1 hunks)
  • src/components/mapillary/MapillaryImageItem.vue (1 hunks)
  • src/components/mapillary/MapillaryInputs.vue (1 hunks)
  • src/components/mapillary/MapillaryListView.vue (1 hunks)
  • src/components/mapillary/MapillarySelectedMessagePanel.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceForm.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceInfo.vue (1 hunks)
  • src/components/mapillary/MapillaryTable.vue (1 hunks)
  • src/components/mapillary/languageOptions.ts (1 hunks)
  • src/composables/useBotStatus.spec.ts (1 hunks)
  • src/composables/useMapillary.ts (1 hunks)
  • src/main.ts (1 hunks)
  • src/stores/mapillary.store.ts (1 hunks)
  • src/types/mapillary.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
  • vite.config.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • .prettierrc.json
🧰 Additional context used
🧬 Code graph analysis (4)
src/composables/useMapillary.ts (2)
src/stores/mapillary.store.ts (1)
  • useMapillaryStore (1-111)
src/types/mapillary.ts (1)
  • MapillaryApiResponse (22-24)
src/types/mapillary.ts (1)
auto-imports.d.ts (7)
  • MapillaryImage (119-119)
  • MapillaryApiResponse (119-119)
  • Metadata (119-119)
  • MetadataKey (119-119)
  • MetadataValue (119-119)
  • MapillaryItem (119-119)
  • Layout (119-119)
auto-imports.d.ts (4)
src/types/mapillary.ts (1)
  • MetadataKey (37-37)
src/components/mapillary/languageOptions.ts (1)
  • languageOptions (1-4)
src/composables/useMapillary.ts (1)
  • useMapillary (1-82)
src/stores/mapillary.store.ts (1)
  • useMapillaryStore (1-111)
src/stores/mapillary.store.ts (2)
auto-imports.d.ts (4)
  • MapillaryItem (119-119)
  • Layout (119-119)
  • MetadataKey (119-119)
  • MetadataValue (119-119)
src/types/mapillary.ts (4)
  • MapillaryItem (40-45)
  • Layout (47-47)
  • MetadataKey (37-37)
  • MetadataValue (38-38)
🔇 Additional comments (37)
src/composables/useBotStatus.spec.ts (1)

1-1: LGTM! Import reordering for consistency.

The alphabetical reordering of imports has no functional impact.

env.d.ts (1)

7-7: LGTM! Formatting adjustment.

End-of-file newline formatting has no functional impact.

vite.config.ts (2)

16-16: LGTM! Tailwind plugin deduplicated.

Ensuring tailwindcss() is invoked only once prevents double initialization.


56-60: LGTM! Mapillary proxy correctly placed.

The new /api/mapillary proxy is correctly positioned before the catch-all /api proxy, ensuring specific routes are matched first.

src/components/BotActions.vue (1)

17-20: LGTM! Template formatting improved.

Multiline formatting enhances readability without changing behavior.

Also applies to: 48-54, 57-62

src/components/JobStatusTag.vue (1)

27-30: Readability tweak LGTM.

Splitting v-if and class across lines improves clarity; no functional impact.

src/types/mapillary.ts (1)

47-47: Layout union is clear.

Type is concise and future-proof for additional layouts later.

prettier.config.ts (2)

35-35: Config export LGTM.

Once the $schema typing is fixed, default export is fine.


18-18: Verify plugin version and .vue support for import organization

Confirm that prettier-plugin-organize-imports is at a version compatible with Prettier 3.x and test its behavior on .vue files:

#!/bin/bash
# Show plugin version
jq -r '.devDependencies["prettier-plugin-organize-imports"]' package.json
# Check import-organizing rules in .vue files
rg -n "simple-import-sort|import/order|organize-imports" -g "*.vue"
package.json (4)

41-42: Consider the impact of prettier-plugin-organize-imports.

The addition of prettier-plugin-organize-imports will automatically reorganize imports when Prettier runs. This can cause large diffs in existing files if imports aren't already organized according to the plugin's rules.

Ensure the team is aware this plugin will reorder imports across the codebase, and consider running Prettier on all files in this PR to avoid surprise changes in future commits.


45-46: TypeScript 5.9.3 uses tilde versioning.

TypeScript is pinned with ~5.9.3, which allows only patch updates (5.9.x). This is a good practice for stability but means you'll need to manually update for minor version bumps.


33-33: Confirmed: @vue/eslint-config-prettier@^10.2.0 is valid and up-to-date. No changes required.


15-19: PrimeVue dependencies are up-to-date and secure. All PrimeVue packages are on the latest stable version (4.4.0) and no security advisories were found.

tsconfig.app.json (3)

12-17: Strict type checking enabled.

Enabling strict mode with noUnusedLocals and noUnusedParameters is excellent for catching potential bugs early. The setting noPropertyAccessFromIndexSignature: false provides flexibility for accessing properties via index signatures.


2-2: Bun and Vue TypeScript config extends.

Extending both @tsconfig/bun and @vue/tsconfig/tsconfig.dom.json provides appropriate defaults for a Bun + Vue environment. Good choice for consistency.


26-29: Alias configuration verified. Imports using @/… match the tsconfig.app.json mapping and no improper alias usages were found.

src/components/Header.vue (2)

13-15: UI updates look good.

The header title simplification from "CuratorBot - Toolforge Jobs" to "Curator" and the padding class change from md:px-52 to lg:px-52 align with the broader UI consolidation mentioned in the PR summary.


7-9: No additional error handling required
authStore.checkAuth() already encapsulates its own try–catch (logging and reset) and a finally block, so the call site need not be wrapped again.

src/main.ts (1)

1-2: Import reordering aligns with Prettier plugin.

Moving the PrimeIcons CSS import after the assets import is likely due to prettier-plugin-organize-imports. This ensures consistent import ordering across the codebase and may also affect CSS cascade order (assets styles load first).

src/components/mapillary/languageOptions.ts (1)

1-4: Limited language support.

Currently only English and German are supported. Consider documenting whether additional languages are planned, or if this is intentionally limited to these two languages for the initial Mapillary integration.

src/components/BotsDataTable.vue (1)

22-81: LGTM on formatting changes.

The reformatting of Column components improves readability without altering functionality. The multi-line format makes the template structure clearer.

src/components/mapillary/MapillarySequenceForm.vue (1)

25-25: No action needed—.trim() is safe on sequenceId. It’s declared as ref<string> and initialized to a string literal ('tulzukst7vufhdo1e4z60f'), so it can’t be null or undefined.

src/components/BotsTable.vue (2)

85-92: Formatting change only.

Template reformatting with no functional impact.


95-100: Formatting change only.

Template reformatting with no functional impact.

src/stores/mapillary.store.ts (1)

1-9: Store structure is well-organized.

The store follows Pinia composition API patterns correctly with clear separation of state, actions, and getters. The $reset function properly reinitializes all state to defaults.

Also applies to: 16-23, 26-28, 38-62, 66-77, 79-111

src/components/mapillary/MapillarySequenceInfo.vue (2)

22-29: External link security is correct.

The Mapillary profile link correctly includes target="_blank" with rel="noopener noreferrer" to prevent security vulnerabilities.


1-49: Component structure is clean and functional.

The component correctly uses the Mapillary store and renders sequence information with appropriate conditional logic.

src/components/mapillary/MapillaryGridView.vue (3)

42-42: Date formatting may need timezone consideration.

Using toLocaleString() without arguments will use the user's browser locale and timezone. This is fine for display purposes, but ensure users understand the timezone context if needed.

Consider if timezone display is important for your use case. If so, you can pass options:

new Date(item.image.captured_at).toLocaleString(undefined, { 
  timeZone: 'UTC',
  timeZoneName: 'short' 
})

46-54: External link security is correct.

The Mapillary image link properly includes target="_blank" with rel="noopener noreferrer" for security.


1-5: Grid view component is well-structured.

The component correctly handles item selection, displays metadata, and provides a clean grid interface for browsing Mapillary images.

Also applies to: 9-23, 27-41, 55-59

.eslintrc-auto-import.json (1)

18-21: ESLint auto-import configuration updated correctly.

The new Mapillary-related globals (types, composables, and stores) are properly registered for ESLint auto-import support, consistent with the new feature additions.

Also applies to: 24-26, 63-63, 114-115

src/App.vue (2)

1-3: Ensure ref is in scope.

If auto-imports aren’t configured for Vue reactivity APIs, import it.

 <script setup lang="ts">
-const activeTab = ref('mapillary')
+import { ref } from 'vue' // if not using auto-imports
+const activeTab = ref('mapillary')
 </script>

9-25: Use v-model (v-model:value) for Tabs binding

Using only :value makes the Tabs controlled but won't update activeTab on click unless the component emits update:value; switch to the library’s v-model syntax (or wire the update event) so UI clicks update the bound state.

-      <Tabs
-        :value="activeTab"
-        class="mt-4"
-      >
+      <Tabs v-model:value="activeTab" class="mt-4">
         <TabList>
           <Tab value="bots">CuratoBot jobs</Tab>
           <Tab value="mapillary">Mapillary Sequence Viewer</Tab>
         </TabList>
src/composables/useMapillary.ts (1)

45-49: Verify captured_at type and normalize before sorting
In src/composables/useMapillary.ts (lines 45–49), subtracting image.captured_at directly may yield NaN or incorrect order if it’s an ISO string or epoch seconds. Confirm its exact type in MapillaryApiResponse and convert to a millisecond timestamp (e.g. via Date.parse or multiplying seconds) before sorting.

src/components/mapillary/MapillaryImageItem.vue (1)

15-21: Display user-friendly one-based index
If index is coming from a zero-based v-for, update the template to {{ index + 1 }} instead of {{ index }} so users see “1, 2, 3…” rather than “0, 1, 2…”. Confirm in the parent component how index is passed.

auto-imports.d.ts (1)

1-217: Auto-generated file - no manual review required.

This file is generated by unplugin-auto-import and should not be manually edited. The additions correctly reflect the new Mapillary types, composables, and utilities introduced in this PR. Changes will be regenerated automatically when the source files are modified.

components.d.ts (1)

1-54: Auto-generated file - no manual review required.

This file is generated by unplugin-vue-components and should not be manually edited. The additions correctly register the new Mapillary components and additional PrimeVue components used in this PR for type-safe template usage. Changes will be regenerated automatically when components are added or removed.

Comment on lines +27 to +43
files: ['**/*.{ts,vue}'],
languageOptions: {
parser: typescriptParser,
parser: vueParser,
parserOptions: {
project: 'tsconfig.app.json',
ecmaVersion: 'latest',
parser: tseslint.parser,
sourceType: 'module',
extraFileExtensions,
projectService: true,
tsconfigRootDir: import.meta.dirname,
extraFileExtensions: ['.vue'],
},
globals: {
...globals.browser,
...autoImportGlobals.globals,
},
},
plugins: {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use vue parser only for .vue; ts parser for .ts

Applying vue-eslint-parser to .ts files can cause parse errors and disable TS rules. Split per-file configs.

 export default defineConfig(
   {
     ignores: [
@@
   prettierConfig,
-  {
-    files: ['**/*.{ts,vue}'],
-    languageOptions: {
-      parser: vueParser,
-      parserOptions: {
-        ecmaVersion: 'latest',
-        parser: tseslint.parser,
-        sourceType: 'module',
-        projectService: true,
-        tsconfigRootDir: import.meta.dirname,
-        extraFileExtensions: ['.vue'],
-      },
-      globals: {
-        ...globals.browser,
-        ...autoImportGlobals.globals,
-      },
-    },
-    plugins: {
-      '@typescript-eslint': tseslint.plugin,
-    },
-    rules: {
+  // Vue SFCs
+  {
+    files: ['**/*.vue'],
+    languageOptions: {
+      parser: vueParser,
+      parserOptions: {
+        ecmaVersion: 'latest',
+        parser: tseslint.parser,
+        sourceType: 'module',
+        projectService: true,
+        tsconfigRootDir: TS_ROOT,
+      },
+      globals: { ...globals.browser, ...autoImportGlobals.globals },
+    },
+    plugins: { '@typescript-eslint': tseslint.plugin },
+    rules: {
       'vue/multi-word-component-names': 'off',
       'vue/singleline-html-element-content-newline': 'off',
       'vue/html-indent': 'off',
       '@typescript-eslint/ban-ts-comment': 'off',
       '@typescript-eslint/no-unsafe-assignment': 'off',
       '@typescript-eslint/no-explicit-any': 'off',
       'comma-dangle': ['error', 'always-multiline'],
@@
       '@typescript-eslint/no-unsafe-argument': 'off',
     },
   },
+  // TypeScript files
+  {
+    files: ['**/*.ts'],
+    languageOptions: {
+      parser: tseslint.parser,
+      parserOptions: {
+        ecmaVersion: 'latest',
+        sourceType: 'module',
+        projectService: true,
+        tsconfigRootDir: TS_ROOT,
+      },
+      globals: { ...globals.browser, ...autoImportGlobals.globals },
+    },
+    plugins: { '@typescript-eslint': tseslint.plugin },
+  },
 )

Also applies to: 29-37

Comment on lines +3 to +5
const config: Config = {
$schema: 'https://json.schemastore.org/prettierrc',
semi: false,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Type error: $schema is not in Prettier’s Config type.

This will fail TS checking. Use satisfies or remove $schema.

-const config: Config = {
-  $schema: 'https://json.schemastore.org/prettierrc',
+const config = {
+  $schema: 'https://json.schemastore.org/prettierrc',
   semi: false,
   singleQuote: true,
   ...
-}
+} satisfies Config

Or drop $schema if you don’t need editor schema hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const config: Config = {
$schema: 'https://json.schemastore.org/prettierrc',
semi: false,
import type { Config } from 'prettier';
const config = {
$schema: 'https://json.schemastore.org/prettierrc',
semi: false,
singleQuote: true,
// ...
} satisfies Config;
export default config;
🤖 Prompt for AI Agents
In prettier.config.ts around lines 3 to 5, the config object includes a $schema
property which is not part of Prettier's Config type and causes a TypeScript
error; fix it by either removing the $schema entry if you don't need editor JSON
schema hints, or keep $schema but change the declaration so the object either
uses the `satisfies Config` operator (e.g. const config = { ... } satisfies
Config) or cast the object to unknown as Config, ensuring TypeScript no longer
reports `$schema` as an excess property while preserving the schema hint.

:value="botsStore.bots"
:loading="isLoading"
stripedRows
striped-rows
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the correct prop name for PrimeVue DataTable.

The prop was changed from stripedRows to striped-rows. PrimeVue typically uses camelCase for prop names in script but kebab-case is valid in templates. Verify that striped-rows is correctly recognized by the DataTable component in your PrimeVue version.


🌐 Web query:

PrimeVue DataTable striped rows prop name

💡 Result:

The DataTable prop is named stripedRows (use as a boolean attribute, e.g. ). [1][2]

Sources:
[1] PrimeVue DataTable docs (Tailwind example) — stripedRows usage.
[2] PrimeVue DataTable docs (primevue.org) — DataTable API and examples.


Replace striped-rows with stripedRows
The DataTable prop is named stripedRows in PrimeVue and must be used as a boolean attribute (e.g. <DataTable :value="items" stripedRows>). (v3.primevue.org)

🤖 Prompt for AI Agents
In src/components/BotsDataTable.vue around line 18, the template uses the
incorrect prop name `striped-rows`; replace it with the PrimeVue boolean prop
`stripedRows` (i.e., use the camelCase attribute `stripedRows` on the DataTable
component) so it is recognized correctly; ensure it is used as a boolean
attribute (no value binding needed) or as a bound boolean (e.g.,
:stripedRows="true") consistent with other props in the file.

Comment on lines 37 to 46
:disabled="store.selectedCount === 0"
:label="
store.showSelectedOnly && store.selectedCount > 0
? 'Show all items'
: 'Show only selected'
"
icon="pi pi-filter"
class="rounded-md!"
@click="() => (store.showSelectedOnly = !store.showSelectedOnly)"
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid inline arrow fn and fix class typo.

Inline arrow allocates each render; class "rounded-md!" looks accidental.

-        <Button
+        <Button
           :disabled="store.selectedCount === 0"
           :label="
             store.showSelectedOnly && store.selectedCount > 0
               ? 'Show all items'
               : 'Show only selected'
           "
           icon="pi pi-filter"
-          class="rounded-md!"
-          @click="() => (store.showSelectedOnly = !store.showSelectedOnly)"
+          class="rounded-md"
+          @click="store.showSelectedOnly = !store.showSelectedOnly"
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
:disabled="store.selectedCount === 0"
:label="
store.showSelectedOnly && store.selectedCount > 0
? 'Show all items'
: 'Show only selected'
"
icon="pi pi-filter"
class="rounded-md!"
@click="() => (store.showSelectedOnly = !store.showSelectedOnly)"
/>
<Button
:disabled="store.selectedCount === 0"
:label="
store.showSelectedOnly && store.selectedCount > 0
? 'Show all items'
: 'Show only selected'
"
icon="pi pi-filter"
class="rounded-md"
@click="store.showSelectedOnly = !store.showSelectedOnly"
/>
🤖 Prompt for AI Agents
In src/components/mapillary/MapillaryTable.vue around lines 37 to 46, the button
uses an inline arrow function and has a stray "!" in the class name; replace the
inline arrow expression (@click="() => (store.showSelectedOnly =
!store.showSelectedOnly)") with a component method or a referenced handler
(e.g., @click="toggleShowSelectedOnly") defined in methods or setup so it isn't
reallocated on every render, and remove the accidental exclamation from the
class (change "rounded-md!" to "rounded-md").

Comment on lines +15 to +23
const validateSequenceId = (sequenceId: string): boolean => {
// Clear previous error
store.error = null

const trimmed = sequenceId.trim()
if (!trimmed) {
store.error = 'Please enter a sequence ID'
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Input not normalized: trim and persist the cleaned value.

validateSequenceId trims but loadSequence later uses the untrimmed store value. Persist the trimmed value before validating.

Apply:

-  const validateSequenceId = (sequenceId: string): boolean => {
+  const validateSequenceId = (sequenceId: string): boolean => {
     // Clear previous error
     store.error = null

-    const trimmed = sequenceId.trim()
+    const trimmed = sequenceId.trim()
     if (!trimmed) {
       store.error = 'Please enter a sequence ID'
       return false
     }
-    const pattern = /^[a-zA-Z0-9_-]+$/
-    if (!pattern.test(trimmed)) {
+    if (!SEQ_ID_RE.test(trimmed)) {
       store.error = 'Sequence ID can only contain letters, numbers, underscores, and hyphens'
       return false
     }
     return true
   }

And in loadSequence:

-    const sequenceId = store.sequenceId
-    if (!validateSequenceId(sequenceId)) return
+    const sequenceId = store.sequenceId.trim()
+    store.sequenceId = sequenceId
+    if (!validateSequenceId(sequenceId)) return

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/composables/useMapillary.ts around lines 15 to 23, validateSequenceId
trims the input but does not persist the cleaned value back to the store and
loadSequence later uses the original untrimmed value; update validateSequenceId
to assign the trimmed value into store.sequenceId before performing validation,
and update loadSequence to use a trimmed version of store.sequenceId (e.g., read
const seq = store.sequenceId.trim() and use seq) so the normalized value is
consistently used throughout.

@DaxServer DaxServer marked this pull request as draft October 10, 2025 14:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

♻️ Duplicate comments (3)
src/components/mapillary/MapillarySequenceForm.vue (1)

7-10: Form submission prevention now handled correctly.

The previous review comment requested preventing default form submission. This has been addressed by implementing handleSubmit with e.preventDefault(). The current approach works correctly.

src/stores/mapillary.store.ts (1)

32-38: Non-null assertion remains unsafe.

The code still uses non-null assertions items.value[id]! (lines 33-34), which will throw if the ID doesn't exist. This is the same issue raised in previous reviews but with Record access instead of find().

Apply this diff to add safety:

 const updateItem = (id: string, key: MetadataKey, value: MetadataValue) => {
-  const meta = items.value[id]!.meta
-  items.value[id]!.meta = {
-    ...meta,
-    [key]: value,
-  }
+  const item = items.value[id]
+  if (!item) {
+    console.error(`Item with id ${id} not found`)
+    return
+  }
+  item.meta = {
+    ...item.meta,
+    [key]: value,
+  }
 }
src/components/mapillary/MapillaryInputs.vue (1)

34-35: Verify languageOptions is auto-imported and non-empty.

The component references languageOptions without an explicit import (line 34) and assumes it's non-empty with the non-null assertion languageOptions[0]! (line 35).

Based on the AI summary, languageOptions should exist at src/components/mapillary/languageOptions.ts. Verify it's properly exported and configured for auto-import:

#!/bin/bash
# Verify languageOptions exists and is exported
fd -g '**/languageOptions.ts' -x cat {}

# Check if it's configured for auto-import
rg -nC3 'languageOptions' vite.config.ts auto-imports.d.ts
🧹 Nitpick comments (14)
src/App.vue (1)

2-6: Consider removing or populating the empty theme overrides.

The themeOverrides object contains only an empty common property with a commented primaryColor. If theme customization isn't needed yet, remove the object; otherwise, uncomment and configure the values.

Apply this diff to remove the unused override:

-const themeOverrides: GlobalThemeOverrides = {
-  common: {
-    // primaryColor: '#0E47CB',
-  },
-}
-
 const activeTab = ref('mapillary')

Or uncomment to apply the theme:

 const themeOverrides: GlobalThemeOverrides = {
   common: {
-    // primaryColor: '#0E47CB',
+    primaryColor: '#0E47CB',
   },
 }
src/components/mapillary/MapillaryUpload.vue (2)

9-9: Replace any with proper type for type safety.

The render function parameter uses any, which eliminates TypeScript's type checking benefits. Define a proper interface for the row data or infer it from store.displayedItems.

Apply this diff to add a type:

+interface DisplayedItem {
+  meta: {
+    title: string
+  }
+}
+
 const columns = [
   { title: '#', key: 'index' },
   { 
     title: 'Title', 
     key: 'title',
-    render: (row: any) => `File:${row.meta.title}`,
+    render: (row: DisplayedItem) => `File:${row.meta.title}`,
   },
 ]

5-5: Use Naive UI’s index column instead of key
Rows in store.displayedItems don’t include an index field, so { title: '#', key: 'index' } won’t render numbers. Replace it with Naive UI’s built-in index column ({ title: '#', type: 'index' }) or add a render function to display rowIndex + 1.

src/components/wikidata/StatementsList.vue (1)

2-3: Consider removing explicit imports if auto-import is configured.

The PR introduces auto-import configuration via unplugin-vue-components. If PropertyLabel and StatementItem are automatically imported elsewhere in the codebase, these explicit imports may be redundant.

Apply this diff if auto-import handles these components:

-import PropertyLabel from '@/components/wikidata/PropertyLabel.vue'
-import StatementItem from '@/components/wikidata/StatementItem.vue'
-
 defineProps<{ statements: Statement[] }>()
src/components/wikidata/useWikidata.ts (2)

1-12: Consider scalability for property mappings.

The hardcoded property mapping covers 11 common Wikidata properties. As the feature evolves, this may require frequent updates. Consider:

  • Documenting the criteria for which properties are included
  • Adding a mechanism to dynamically fetch labels from Wikidata API if the mapping grows significantly
  • Centralizing these mappings in a configuration file if they're shared across components

20-28: LGTM! Consider logging for missing mappings.

The implementation is clean and safe with nullish coalescing. For debugging during development, consider logging when a property or entity isn't found:

 export const useWikidata = () => {
-  const getPropertyLabel = (property: string) => properties[property] ?? ''
-  const getEntityLabel = (entity: string) => entities[entity] ?? ''
+  const getPropertyLabel = (property: string) => {
+    if (!properties[property]) {
+      console.warn(`Unknown Wikidata property: ${property}`)
+    }
+    return properties[property] ?? ''
+  }
+  const getEntityLabel = (entity: string) => {
+    if (!entities[entity]) {
+      console.warn(`Unknown Wikidata entity: ${entity}`)
+    }
+    return entities[entity] ?? ''
+  }

   return {
     getPropertyLabel,
     getEntityLabel,
   }
 }

This helps identify missing mappings early.

src/components/mapillary/MapillarySequenceEdit.vue (3)

22-37: Remove or implement the commented toggle button.

The commented filter button suggests incomplete functionality. Either:

  • Implement the feature if it's planned
  • Remove the code if it's not needed

64-71: Consider MapillaryListView API design.

Passing a single-item array [item] to MapillaryListView in a loop seems unusual. If MapillaryListView is designed to render multiple items, consider:

  • Rendering all store.displayedItems at once: <MapillaryListView :items="store.displayedItems" />
  • Or renaming to MapillaryListItem if it's meant for single items

This pattern may cause performance issues with large lists due to component overhead.


38-49: Centralize stepper states
Define a single enum or constant object for the various stepper values (currently '2', '3', '4', '5') and replace all hardcoded strings (store.stepper = '…') with these named constants to improve maintainability.

src/stores/mapillary.store.ts (2)

46-57: Consider validation for language codes.

The setGlobalLanguage function accepts any string. Consider validating that the language code follows a standard format (e.g., ISO 639-1 like 'en', 'es') to prevent invalid values.


86-86: Clarify or remove displayRows.

The displayRows getter returns a constant value (5) regardless of stepper state. This appears to be a placeholder or the ternary is unnecessary. Consider:

  • Removing it if unused
  • Documenting its purpose if it's a feature in progress
  • Making the value configurable if it represents pagination
src/components/mapillary/MapillaryPreview.vue (1)

41-43: Add error handling for SDC loading.

The loadSDC() call in onMounted lacks visible error handling. If loading fails, users won't see any feedback. Consider adding try-catch or checking if loadSDC handles errors internally.

 onMounted(async () => {
-  await loadSDC()
+  try {
+    await loadSDC()
+  } catch (error) {
+    console.error('Failed to load SDC data:', error)
+    // Optionally show user-facing error message
+  }
 })
src/components/mapillary/MapillaryGridView.vue (1)

7-28: Mixed icon library usage detected.

The component uses PrimeIcons classes (pi pi-check on line 27, pi pi-external-link on line 53) while the project is migrating to Naive UI and using @vicons/ionicons5. Consider replacing these with consistent icon components from the new icon library.

Example for check icon:

-<i class="pi pi-check text-white absolute top-2 left-2 text-xl!"></i>
+<n-icon class="text-white absolute top-2 left-2" size="20">
+  <CheckmarkOutline />
+</n-icon>

Import the icon:

<script setup lang="ts">
+import { CheckmarkOutline, OpenOutline } from '@vicons/ionicons5'
 defineProps<{ items: MapillaryItem[] }>()
src/components/Header.vue (1)

4-6: Add error handling for auth check.

The async checkAuth() call lacks error handling. If authentication fails, users won't see any feedback.

 onMounted(async () => {
-  await authStore.checkAuth()
+  try {
+    await authStore.checkAuth()
+  } catch (error) {
+    console.error('Auth check failed:', error)
+  }
 })
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03225ea and 8d1c296.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (39)
  • .eslintrc-auto-import.json (3 hunks)
  • auto-imports.d.ts (7 hunks)
  • components.d.ts (1 hunks)
  • package.json (1 hunks)
  • postcss.config.ts (1 hunks)
  • src/App.vue (1 hunks)
  • src/assets/main.css (1 hunks)
  • src/components/BotActions.vue (1 hunks)
  • src/components/BotsDataTable.vue (1 hunks)
  • src/components/BotsTable.vue (2 hunks)
  • src/components/Footer.vue (1 hunks)
  • src/components/Header.vue (1 hunks)
  • src/components/JobStatusTag.vue (1 hunks)
  • src/components/mapillary/MapillaryBatchPanel.vue (1 hunks)
  • src/components/mapillary/MapillaryGridView.vue (1 hunks)
  • src/components/mapillary/MapillaryImageItem.vue (1 hunks)
  • src/components/mapillary/MapillaryInputs.vue (1 hunks)
  • src/components/mapillary/MapillaryPreview.vue (1 hunks)
  • src/components/mapillary/MapillarySelectedMessagePanel.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceEdit.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceForm.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceInfo.vue (1 hunks)
  • src/components/mapillary/MapillarySequenceSelect.vue (1 hunks)
  • src/components/mapillary/MapillaryTable.vue (1 hunks)
  • src/components/mapillary/MapillaryUpload.vue (1 hunks)
  • src/components/mapillary/languageOptions.ts (1 hunks)
  • src/components/wikidata/PropertyLabel.vue (1 hunks)
  • src/components/wikidata/StatementItem.vue (1 hunks)
  • src/components/wikidata/StatementsList.vue (1 hunks)
  • src/components/wikidata/VR.vue (1 hunks)
  • src/components/wikidata/ValueRenderer.vue (1 hunks)
  • src/components/wikidata/useWikidata.ts (1 hunks)
  • src/composables/useMapillary.ts (1 hunks)
  • src/main.ts (0 hunks)
  • src/stores/mapillary.store.ts (1 hunks)
  • src/types/mapillary.ts (1 hunks)
  • src/types/wikidata.ts (1 hunks)
  • tsconfig.app.json (1 hunks)
  • vite.config.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • src/main.ts
✅ Files skipped from review due to trivial changes (1)
  • postcss.config.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/mapillary/languageOptions.ts
  • src/composables/useMapillary.ts
  • src/components/BotsTable.vue
  • src/components/mapillary/MapillaryBatchPanel.vue
  • src/components/JobStatusTag.vue
  • src/components/mapillary/MapillaryTable.vue
🧰 Additional context used
🧬 Code graph analysis (4)
src/types/mapillary.ts (1)
src/types/wikidata.ts (1)
  • Statement (113-119)
src/stores/mapillary.store.ts (1)
src/types/mapillary.ts (4)
  • MapillaryItem (41-47)
  • Layout (49-49)
  • MetadataKey (38-38)
  • MetadataValue (39-39)
src/types/wikidata.ts (1)
auto-imports.d.ts (22)
  • WikibaseEntityType (126-126)
  • DataValueEntityId (126-126)
  • DataValueMonolingualText (126-126)
  • DataValueQuantity (126-126)
  • DataValueTime (126-126)
  • DataValueGlobeCoordinate (126-126)
  • StringDataValue (126-126)
  • EntityIdDataValue (126-126)
  • MonolingualTextDataValue (126-126)
  • QuantityDataValue (126-126)
  • TimeDataValue (126-126)
  • GlobeCoordinateDataValue (126-126)
  • UrlDataValue (126-126)
  • DataValue (126-126)
  • SnakType (126-126)
  • ValueSnak (126-126)
  • SomeValueSnak (126-126)
  • NoValueSnak (126-126)
  • Snak (126-126)
  • Reference (126-126)
  • Rank (126-126)
  • Statement (126-126)
auto-imports.d.ts (4)
src/components/mapillary/languageOptions.ts (1)
  • languageOptions (1-4)
src/composables/useMapillary.ts (1)
  • useMapillary (1-185)
src/stores/mapillary.store.ts (1)
  • useMapillaryStore (1-122)
src/components/wikidata/useWikidata.ts (1)
  • useWikidata (20-28)
🔇 Additional comments (34)
src/types/mapillary.ts (1)

1-49: Types look solid

Contracts align with usage in store/composable (coords order noted, captured_at numeric). LGTM.

.eslintrc-auto-import.json (1)

8-62: Note: Tabler icons referenced

Globals reference Tabler icons (IconEye, IconFilter, IconSearch, IconUpload). Ensure @vicons/tabler is installed (see package.json comment) to avoid missing modules during typecheck/build.

auto-imports.d.ts (1)

10-14: Verify Tabler icon dependency

These globals import from '@vicons/tabler'. Add the package if not yet present to prevent module resolution errors.

package.json (1)

21-51: Add missing @vicons/tabler dependency
Auto-generated imports in auto-imports.d.ts reference Tabler icons but the package isn’t listed. Add to devDependencies:

   "devDependencies": {
+    "@vicons/tabler": "^0.13.0",

Align the version with your existing @vicons/ionicons5 entry.

src/components/mapillary/MapillarySelectedMessagePanel.vue (1)

1-27: LGTM!

The component correctly displays the selection count and disables the "Start editing" button when no items are selected. The state transitions through store.stepper are appropriate for the workflow.

src/components/wikidata/VR.vue (1)

1-27: LGTM!

The component correctly handles all three snak types (value, novalue, somevalue) with appropriate rendering logic. The conditional structure is clear and the ValueRenderer delegation is appropriate.

src/components/wikidata/StatementItem.vue (1)

1-31: LGTM!

The component correctly renders the main snak and qualifiers with proper key bindings and nested structure. The layout and conditional rendering logic are appropriate.

src/components/mapillary/MapillarySequenceInfo.vue (1)

1-33: LGTM!

The component correctly displays sequence metadata with proper conditional rendering for the creator section. The external link includes appropriate security attributes (rel="noopener noreferrer") to prevent tabnabbing attacks.

src/components/wikidata/StatementsList.vue (1)

7-23: LGTM!

The column definitions using h() render functions are appropriate for programmatic component creation. The use of Tailwind's arbitrary variant syntax (text-right!) is valid and the overall structure is clean.

src/components/wikidata/PropertyLabel.vue (2)

1-4: LGTM!

Clean composition API setup with proper type safety.


6-15: LGTM!

Proper external link security attributes and clear display of both label and property ID for reference.

src/components/mapillary/MapillarySequenceEdit.vue (2)

1-6: LGTM!

Clean setup with proper separation between store state and actions.


8-20: LGTM!

Clear user feedback about which items are being displayed.

src/stores/mapillary.store.ts (4)

1-26: LGTM!

State declarations are well-typed and organized. Using Record<string, MapillaryItem> for items improves lookup performance compared to array-based storage.


40-44: LGTM!

Safe iteration and update pattern without non-null assertions.


59-70: LGTM!

Comprehensive reset function that properly reinitializes all state properties.


88-122: LGTM!

Clear and comprehensive store interface with well-organized exports.

vite.config.ts (4)

8-9: LGTM!

Correct setup of Tailwind CSS v4 and Naive UI resolver. Based on learnings, Tailwind v4 uses the first-party Vite plugin which provides better performance.

Also applies to: 16-16


17-36: LGTM!

Proper Naive UI auto-import configuration with type-only imports correctly marked.


37-43: LGTM!

Components auto-import properly configured for Naive UI with TypeScript declarations enabled.


67-72: Verify callback rewrite usage and backend expectations. No references to /callback/wikimedia or /auth/callback were found in the frontend code; ensure the backend is configured to handle /auth/callback and that your OAuth flow actually uses /callback/wikimedia as the public callback URL.

src/components/mapillary/MapillaryPreview.vue (3)

1-6: LGTM!

Clean component setup with proper separation of store state and composable logic.


46-82: LGTM!

Clear preview UI with proper button states and pagination configuration.


8-39: No HTML injection risk in wikitext rendering
Vue’s h('pre', …, wikitext(row)) escapes text by default, so any HTML in description.text or categories is shown verbatim inside <pre>, preventing XSS.

src/components/mapillary/MapillaryGridView.vue (3)

1-5: LGTM!

Clean component setup with proper prop typing.


29-33: LGTM!

Proper image component usage with descriptive alt text.


34-57: LGTM!

Card footer provides clear information with proper external link handling and locale-aware time formatting.

src/components/Header.vue (2)

10-28: LGTM!

Clean migration to Naive UI components with proper layout and responsive styling.


29-39: LGTM!

Login button properly handles loading states and provides clear user feedback.

src/components/mapillary/MapillarySequenceSelect.vue (1)

1-81: LGTM! Clean data table implementation.

The component properly configures the Naive UI data table with:

  • Selection controls
  • Interactive image thumbnails that toggle selection
  • Well-structured metadata display with external links
  • Proper security attributes (noopener noreferrer) on external links
  • Integration with the Mapillary store
tsconfig.app.json (1)

1-36: LGTM! Well-configured TypeScript settings.

The configuration properly:

  • Extends from Bun and Vue base configs
  • Enables strict type checking with appropriate compiler flags
  • Includes all necessary type declaration files
  • Sets up path aliases correctly
  • Includes types for Vite, Bun, Node, and Naive UI
src/components/mapillary/MapillaryImageItem.vue (1)

1-67: Well-structured component with proper store integration.

The component effectively:

  • Uses a clean grid layout for visual hierarchy
  • Properly binds selection state and title to the store
  • Implements lazy loading for images
  • Includes secure external links with noopener noreferrer
  • Integrates MapillaryInputs with conditional enable/disable based on global state

Also applies to: 70-131

src/types/wikidata.ts (1)

1-119: LGTM! Comprehensive Wikidata type definitions.

The type definitions accurately represent the Wikidata data model with:

  • Proper union types for DataValue variants
  • Correct discriminated unions using the type field
  • Appropriate string types for Wikidata-specific formats (amounts, times, URLs)
  • Complete coverage of Snak, Reference, and Statement structures
components.d.ts (1)

1-56: Auto-generated file - no review needed.

This file is automatically generated by unplugin-vue-components and should not be manually edited. The component declarations correctly reflect the addition of Mapillary and Wikidata components, and the migration from PrimeVue to Naive UI.

Comment on lines +4 to +6
.row-selected-global td {
@apply bg-green-50!;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix Tailwind important syntax; wrap in utilities layer

Use the important modifier as a prefix, not a suffix. Prefer registering custom utilities in the utilities layer for correct order.

Apply:

-/* Add global utility classes here */
-.row-selected-global td {
-  @apply bg-green-50!;
-}
+/* Add global utility classes here */
+@layer utilities {
+  .row-selected-global td {
+    @apply !bg-green-50;
+  }
+}

Based on learnings

🤖 Prompt for AI Agents
In src/assets/main.css around lines 4 to 6 the Tailwind important modifier is
used as a suffix and the custom utility is not registered in the utilities
layer; change the rule to register it inside an @layer utilities block and use
the prefix form of the important modifier: move the .row-selected-global td rule
into @layer utilities and apply the utility with the ! prefix (i.e. @apply
!bg-green-50) so Tailwind emits the important variant in the correct order.

Comment on lines +21 to +77
<n-button
v-if="bot.status.action.type === 'start'"
:severity="bot.status.action.severity"
:label="bot.status.action.label"
icon="pi pi-play"
:type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
size="small"
:loading="jobsStore.starting[bot.type]"
:disabled="isLoading"
@click="onStart(bot.type)"
/>
<Button
>
<template #icon>
<i class="pi pi-play"></i>
</template>
{{ bot.status.action.label }}
</n-button>
<n-button
v-else-if="bot.status.action.type === 'terminate' || bot.status.action.type === 'stop'"
:severity="bot.status.action.severity"
:type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
size="small"
icon="pi pi-stop"
:label="bot.status.action.label"
:loading="jobsStore.deleting[bot.type]"
:disabled="isLoading"
@click="onStop(bot.type)"
/>
<Tag
>
<template #icon>
<i class="pi pi-stop"></i>
</template>
{{ bot.status.action.label }}
</n-button>
<n-tag
v-else-if="bot.status.action.type === 'pending'"
:severity="bot.status.action.severity"
:type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
size="small"
icon="pi pi-spin pi-spinner"
:value="bot.status.action.label"
/>
<Tag v-else severity="danger" size="small" icon="pi pi-question" value="Unknown" />
>
<template #icon>
<i class="pi pi-spin pi-spinner"></i>
</template>
{{ bot.status.action.label }}
</n-tag>
<n-tag
v-else
type="error"
size="small"
>
<template #icon>
<i class="pi pi-question"></i>
</template>
Unknown
</n-tag>
</div>
<div v-else-if="authStore.isAuthenticated && !authStore.isAuthorized">
<Tag severity="danger" size="small" icon="pi pi-lock" value="Not authorized" />
<n-tag
type="error"
size="small"
>
<template #icon>
<i class="pi pi-lock"></i>
</template>
Not authorized
</n-tag>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace PrimeIcons; map severities comprehensively

  • PrimeIcons classes remain (play/stop/spinner/question/lock) but primeicons is no longer installed → broken icons.
  • Map only 'danger'→'error'; other severities (warning/success/info) lose semantic coloring.

Icons with vicons + Naive UI:

-    <n-button
+    <n-button
       v-if="bot.status.action.type === 'start'"
-      :type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
+      :type="severityMap[bot.status.action.severity] ?? 'default'"
       size="small"
       :loading="jobsStore.starting[bot.type]"
       :disabled="isLoading"
       @click="onStart(bot.type)"
     >
-      <template #icon>
-        <i class="pi pi-play"></i>
-      </template>
+      <template #icon><n-icon><IconPlayerPlay /></n-icon></template>
       {{ bot.status.action.label }}
     </n-button>
-    <n-button
+    <n-button
       v-else-if="bot.status.action.type === 'terminate' || bot.status.action.type === 'stop'"
-      :type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
+      :type="severityMap[bot.status.action.severity] ?? 'default'"
       size="small"
       :loading="jobsStore.deleting[bot.type]"
       :disabled="isLoading"
       @click="onStop(bot.type)"
     >
-      <template #icon>
-        <i class="pi pi-stop"></i>
-      </template>
+      <template #icon><n-icon><IconPlayerStop /></n-icon></template>
       {{ bot.status.action.label }}
     </n-button>
-    <n-tag
+    <n-tag
       v-else-if="bot.status.action.type === 'pending'"
-      :type="bot.status.action.severity === 'danger' ? 'error' : 'default'"
+      :type="severityMap[bot.status.action.severity] ?? 'default'"
       size="small"
     >
-      <template #icon>
-        <i class="pi pi-spin pi-spinner"></i>
-      </template>
+      <template #icon><n-icon class="animate-spin"><IconLoader2 /></n-icon></template>
       {{ bot.status.action.label }}
     </n-tag>
-    <n-tag
+    <n-tag
       v-else
       type="error"
       size="small"
     >
-      <template #icon>
-        <i class="pi pi-question"></i>
-      </template>
+      <template #icon><n-icon><IconQuestionMark /></n-icon></template>
       Unknown
     </n-tag>
...
-  <div v-else-if="authStore.isAuthenticated && !authStore.isAuthorized">
-    <n-tag
+  <div v-else-if="authStore.isAuthenticated && !authStore.isAuthorized">
+    <n-tag
       type="error"
       size="small"
     >
-      <template #icon>
-        <i class="pi pi-lock"></i>
-      </template>
+      <template #icon><n-icon><IconLock /></n-icon></template>
       Not authorized
     </n-tag>

Add in script:

// <script setup>
import { IconPlayerPlay, IconPlayerStop, IconLoader2, IconQuestionMark, IconLock } from '@vicons/tabler'

// Map severities to Naive types
const severityMap: Record<string, 'default' | 'primary' | 'info' | 'success' | 'warning' | 'error'> = {
  danger: 'error',
  warning: 'warning',
  success: 'success',
  info: 'primary',
}

Note: ensure @vicons/tabler is installed (see package.json comment).

Comment on lines +21 to +62
const columns = [
{
title: 'Type',
key: 'type',
render: (row: unknown) => {
const typedRow = row as BotRow
if (typedRow?.type) {
return h('span', { class: 'font-bold' }, typedRow.type)
}
return h('span', { class: 'text-gray-400 italic' }, 'Unknown')
},
},
{
title: 'Status',
key: 'status',
render: (row: unknown) => {
const typedRow = row as BotRow
return h(JobStatusTag, { status: typedRow.status })
},
},
{
title: 'Job',
key: 'jobName',
render: (row: unknown) => {
const typedRow = row as BotRow
if (typedRow?.jobName) {
return h('span', { class: 'font-mono text-sm' }, typedRow.jobName)
}
return null
},
},
{
title: 'Command',
key: 'command',
render: (row: unknown) => {
const typedRow = row as BotRow
return h('code', { class: 'text-sm' },
typedRow.command + (typedRow.args ? ' ' + typedRow.args.join(' ') : ''),
)
},
},
]
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make columns reactive to auth; add typing

columns.push(...) runs once at setup. If auth changes (login/logout), the Actions column won’t update. Also, render functions use unknown, losing type-safety.

Apply:

-const columns = [
+const baseColumns: DataTableColumns<Bot> = [
   {
     title: 'Type',
     key: 'type',
-    render: (row: unknown) => {
-      const typedRow = row as BotRow
+    render: (row: Bot) => {
+      const typedRow = row as unknown as BotRow
       if (typedRow?.type) {
         return h('span', { class: 'font-bold' }, typedRow.type)
       }
       return h('span', { class: 'text-gray-400 italic' }, 'Unknown')
     },
   },
   {
     title: 'Status',
     key: 'status',
-    render: (row: unknown) => {
-      const typedRow = row as BotRow
+    render: (row: Bot) => {
+      const typedRow = row as unknown as BotRow
       return h(JobStatusTag, { status: typedRow.status })
     },
   },
   {
     title: 'Job',
     key: 'jobName',
-    render: (row: unknown) => {
-      const typedRow = row as BotRow
+    render: (row: Bot) => {
+      const typedRow = row as unknown as BotRow
       if (typedRow?.jobName) {
         return h('span', { class: 'font-mono text-sm' }, typedRow.jobName)
       }
       return null
     },
   },
   {
     title: 'Command',
     key: 'command',
-    render: (row: unknown) => {
-      const typedRow = row as BotRow
+    render: (row: Bot) => {
+      const typedRow = row as unknown as BotRow
       return h('code', { class: 'text-sm' },
         typedRow.command + (typedRow.args ? ' ' + typedRow.args.join(' ') : ''),
       )
     },
   },
 ]
 
-if (authStore.isAuthorized) {
-  columns.push({
-    title: 'Actions',
-    key: 'actions',
-    render: (row: unknown) => {
-      const typedRow = row as BotRow
-      return h(BotActions, {
-        bot: typedRow,
-        onStart: props.handleStartJob,
-        onStop: props.handleDeleteJob,
-      })
-    },
-  })
-}
+const actionsColumn: DataTableColumns<Bot>[number] = {
+  title: 'Actions',
+  key: 'actions',
+  render: (row: Bot) => {
+    const typedRow = row as unknown as BotRow
+    return h(BotActions, {
+      bot: row as Bot, // pass the full Bot object
+      onStart: props.handleStartJob,
+      onStop: props.handleDeleteJob,
+    })
+  },
+}
+
+const columns = computed<DataTableColumns<Bot>>(() =>
+  authStore.isAuthorized ? [...baseColumns, actionsColumn] : baseColumns
+)

This keeps Actions in sync with auth and improves type safety.

Also applies to: 64-77

🤖 Prompt for AI Agents
In src/components/BotsDataTable.vue around lines 21-62 (and similarly for
64-77), the columns array is created once with render functions typed as unknown
and thus won’t react to auth changes; change it to a reactive/computed columns
value that depends on the auth state (e.g., computed(() =>
baseColumns.concat(auth.value ? actionsColumn : []))) so the Actions column is
added/removed when login/logout happens, and replace render signatures to use
the concrete BotRow type instead of unknown (or use the table column generic) so
render functions receive a properly typed row; ensure any concatenation of args
handles undefined safely and that the computed is used where columns are
consumed.

Comment on lines +10 to 12
<n-layout-footer
class="fixed bottom-0 flex justify-between items-center border-l-0! border-r-0! border-b-0! md:px-52! w-full"
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tailwind important modifier used incorrectly

Suffix form border-l-0! / md:px-52! won’t work. Use ! as a prefix.

-  <n-layout-footer
-    class="fixed bottom-0 flex justify-between items-center border-l-0! border-r-0! border-b-0! md:px-52! w-full"
-  >
+  <n-layout-footer
+    class="fixed bottom-0 flex justify-between items-center !border-l-0 !border-r-0 !border-b-0 md:!px-52 w-full"
+  >

Based on learnings

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<n-layout-footer
class="fixed bottom-0 flex justify-between items-center border-l-0! border-r-0! border-b-0! md:px-52! w-full"
>
<n-layout-footer
class="fixed bottom-0 flex justify-between items-center !border-l-0 !border-r-0 !border-b-0 md:!px-52 w-full"
>
🤖 Prompt for AI Agents
In src/components/Footer.vue around lines 10 to 12, the Tailwind important
modifier is used as a suffix (e.g., border-l-0! and md:px-52!) which is invalid;
update those utilities to use the prefix form so they become !border-l-0 and
!md:px-52 (and any other suffixed utilities in the same element) to ensure the
important modifier is applied correctly across responsive variants.

Comment on lines +15 to +17
<span class="pi pi-github"></span>
&nbsp;
<template
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace PrimeIcons with Naive UI + vicons

PrimeVue/primeicons were removed; <span class="pi ..."> will render nothing. Use <n-icon> with vicons.

Example changes:

-        <span class="pi pi-github"></span>
+        <n-icon><LogoGithub /></n-icon>

Add import in script:

// in <script setup>
import { LogoGithub } from '@vicons/ionicons5'

Alternatively use Tabler’s BrandGithub if you add @vicons/tabler.

🤖 Prompt for AI Agents
In src/components/Footer.vue around lines 15 to 17, the code uses a PrimeIcons
span (<span class="pi pi-github">) which no longer exists; replace it with Naive
UI's n-icon plus a vicons icon. Import the desired vicon in the <script setup>
(for example LogoGithub from @vicons/ionicons5 or BrandGithub from
@vicons/tabler), then replace the span with <n-icon :component="ImportedIcon" />
(or equivalent binding) and remove the old pi class usage so the GitHub icon
renders correctly with Naive UI.

Comment on lines +68 to +69
<span class="text-gray-500">Compass Angle</span>
<div>{{ image.compass_angle.toFixed(1) }}°</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null check for compass_angle to prevent runtime error.

The previous issue was that a v-if="image.compass_angle" check would hide 0° values. The v-if was removed, but now there's no null/undefined check at all. Calling .toFixed(1) on null or undefined will throw a runtime error.

Apply this diff to add a proper null check:

-          <div>
+          <div v-if="image.compass_angle != null">
             <span class="text-gray-500">Compass Angle</span>
             <div>{{ image.compass_angle.toFixed(1) }}°</div>
           </div>

This checks for both null and undefined while allowing 0 to be displayed correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span class="text-gray-500">Compass Angle</span>
<div>{{ image.compass_angle.toFixed(1) }}°</div>
<div v-if="image.compass_angle != null">
<span class="text-gray-500">Compass Angle</span>
<div>{{ image.compass_angle.toFixed(1) }}°</div>
</div>
🤖 Prompt for AI Agents
In src/components/mapillary/MapillaryImageItem.vue around lines 68-69, calling
image.compass_angle.toFixed(1) can throw if compass_angle is null or undefined;
change the rendering to first check that image.compass_angle !== null &&
image.compass_angle !== undefined and only then call .toFixed(1) and append the
degree symbol, otherwise render a fallback (e.g., "—" or "N/A") so that 0 is
still displayed correctly.

<span>{{ value.value.amount }} {{ value.value.unit }}</span>
</template>
<template v-else-if="value.type === 'time'">
<span>{{ new Date(value.value.time.replace(/^\+0+/, '')).toISOString().split('T')[0] }}</span>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix time parsing regex to handle all Wikidata time formats.

The regex /^\+0+/ only matches when there's at least one zero after the plus sign. Wikidata times like "+2013-01-01T00:00:00Z" (without leading zeros) won't match, leaving the "+" in the string, which may cause new Date() to fail or behave unexpectedly.

Apply this diff to correctly strip the leading plus sign and any zeros:

-    <span>{{ new Date(value.value.time.replace(/^\+0+/, '')).toISOString().split('T')[0] }}</span>
+    <span>{{ new Date(value.value.time.replace(/^\+0*/, '')).toISOString().split('T')[0] }}</span>

The regex /^\+0*/ matches "+" followed by zero or more "0"s, correctly handling both "+2013..." and "+00002013...".

🤖 Prompt for AI Agents
In src/components/wikidata/ValueRenderer.vue around line 24, the time parsing
regex only strips a '+' when it is followed by one or more zeros (`/^\+0+/`), so
strings like "+2013-01-01T00:00:00Z" keep the '+' and can break Date parsing;
update the replace call to use `/^\+0*/` to remove a leading '+' and any number
of zeros (including none), so replace(/^\+0*/, '') before constructing the Date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant