docs: correct PlatformServiceMixin caching documentation and fix interface comments #166
Open
anomalist
wants to merge 3 commits from platformservicemixin-interface-consolidation
into master
16 changed files with 695 additions and 209 deletions
@ -0,0 +1,474 @@ |
|||
# PlatformServiceMixin Interface Consolidation |
|||
|
|||
**Author**: Matthew Raymer |
|||
**Date**: 2025-08-13 |
|||
**Status**: 🎯 **PLANNING** - Ready for Implementation |
|||
|
|||
## Overview |
|||
|
|||
This document describes the planned consolidation of PlatformServiceMixin interfaces to |
|||
eliminate duplication and ensure consistency between `IPlatformServiceMixin` and |
|||
`ComponentCustomProperties` interfaces. **IMPORTANT**: The planned consolidation will |
|||
introduce a breaking change by removing the deprecated `$updateSettings` method, which |
|||
will cause runtime failures in components that still use it. |
|||
|
|||
## Problem Statement |
|||
|
|||
The current PlatformServiceMixin has two separate interfaces with overlapping methods: |
|||
|
|||
1. **`IPlatformServiceMixin`** - Exported interface for component typing |
|||
2. **`ComponentCustomProperties`** - Vue declaration merging interface |
|||
|
|||
This causes: |
|||
- Duplicate method definitions |
|||
- Inconsistent interface maintenance |
|||
- Confusion about which interface to use |
|||
- Deprecated methods still appearing in interfaces |
|||
|
|||
### ComponentCustomProperties Usage Analysis |
|||
|
|||
**Important Discovery**: `ComponentCustomProperties` is **NOT actually used** anywhere in |
|||
the codebase for runtime functionality. It exists solely for **TypeScript declaration |
|||
merging** to provide: |
|||
|
|||
- Method autocomplete when typing `this.$` in Vue components |
|||
- Type checking for mixin methods |
|||
- IntelliSense support in development environments |
|||
- Compile-time validation that methods exist |
|||
|
|||
All components use PlatformServiceMixin methods by explicitly importing the mixin and |
|||
adding it to the `@Component` decorator. |
|||
|
|||
## Planned Solution: Interface Consolidation |
|||
|
|||
### Single Source of Truth |
|||
|
|||
The `IPlatformServiceMixin` interface will serve as the single source of truth for all |
|||
PlatformServiceMixin methods. The `ComponentCustomProperties` interface will extend this |
|||
interface to ensure complete consistency. |
|||
|
|||
```typescript |
|||
// Single interface definition |
|||
export interface IPlatformServiceMixin { |
|||
// All methods defined here |
|||
} |
|||
|
|||
// Vue declaration merging extends the main interface |
|||
declare module "@vue/runtime-core" { |
|||
interface ComponentCustomProperties extends IPlatformServiceMixin { |
|||
// All methods inherited from IPlatformServiceMixin |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Deprecated Method Removal - PLANNED BREAKING CHANGE |
|||
|
|||
**⚠️ CRITICAL**: The deprecated `$updateSettings` method will be completely removed |
|||
from both interfaces AND the implementation. This is a **PLANNED BREAKING CHANGE** that |
|||
will: |
|||
|
|||
- **Prevent TypeScript compilation** (method not found in interfaces) |
|||
- **Cause runtime crashes** (method not found in mixin implementation) |
|||
- **Break existing functionality** in components that use it |
|||
|
|||
**Methods to Remove**: |
|||
- ❌ **`$updateSettings(changes, did?)`** - Will be completely removed from interfaces and implementation |
|||
|
|||
**Required Replacement Methods**: |
|||
- ✅ **`$saveSettings(changes)`** - for default settings |
|||
- ✅ **`$saveUserSettings(did, changes)`** - for user-specific settings |
|||
- ✅ **`$saveMySettings(changes)`** - for current user's settings |
|||
|
|||
## Current Status: PLANNING PHASE |
|||
|
|||
### What Will Happen |
|||
|
|||
1. **Interface Consolidation**: Will eliminate duplication between interfaces |
|||
2. **Deprecated Method Removal**: Will remove runtime functionality for `$updateSettings` |
|||
3. **Component Migration**: Will be required for components using the removed method |
|||
|
|||
### Impact Assessment |
|||
|
|||
#### Immediate Issues After Implementation |
|||
- **Build failures**: TypeScript compilation errors |
|||
- **Runtime crashes**: `$updateSettings` method not found |
|||
- **Broken functionality**: Settings updates will fail |
|||
|
|||
#### Affected Components |
|||
The following components actively use `$updateSettings` and will break after implementation: |
|||
|
|||
- `NewActivityView.vue` - 6 method references |
|||
- `SearchAreaView.vue` - 2 method references |
|||
- `FeedFilters.vue` - 4 method references |
|||
- `HelpView.vue` - 1 method reference |
|||
- `HelpNotificationsView.vue` - 1 method reference |
|||
- `ContactQRScanShowView.vue` - 2 method references |
|||
- `NewEditAccountView.vue` - 1 method reference |
|||
- `SharedPhotoView.vue` - 1 method reference |
|||
- `UserNameDialog.vue` - 1 method reference |
|||
- `OnboardingDialog.vue` - 2 method references |
|||
|
|||
**Total**: ~20+ method references across multiple components |
|||
|
|||
## Implementation Plan |
|||
|
|||
### Phase 1: Stabilization (Before Breaking Change) |
|||
|
|||
1. **Plan migration strategy** for all affected components |
|||
2. **Create migration script** to automate method replacement |
|||
3. **Update test coverage** for new method patterns |
|||
|
|||
### Phase 2: Interface Consolidation |
|||
|
|||
1. **Consolidate interfaces** - Eliminate duplication between `IPlatformServiceMixin` and `ComponentCustomProperties` |
|||
2. **Remove deprecated method** from interfaces |
|||
3. **Remove deprecated method** from implementation |
|||
|
|||
### Phase 3: Component Migration |
|||
|
|||
1. **Audit all components** using `$updateSettings` |
|||
2. **Categorize usage patterns** (default vs. user-specific settings) |
|||
3. **Replace method calls** with appropriate new methods |
|||
4. **Update tests** to use new method patterns |
|||
5. **Validate functionality** after each change |
|||
|
|||
## Migration Strategy |
|||
|
|||
### Phase 1: Preparation (Before Breaking Change) |
|||
|
|||
1. **Audit all components** using `$updateSettings` |
|||
2. **Categorize usage patterns** (default vs. user-specific settings) |
|||
3. **Create migration plan** for each component |
|||
4. **Update test coverage** for new method patterns |
|||
|
|||
### Phase 2: Interface Consolidation |
|||
|
|||
```typescript |
|||
// Remove from IPlatformServiceMixin interface |
|||
// Remove from ComponentCustomProperties interface |
|||
// Remove implementation from PlatformServiceMixin |
|||
``` |
|||
|
|||
### Phase 3: Component Migration (Systematic) |
|||
|
|||
1. **Replace method calls** with appropriate new methods |
|||
2. **Update tests** to use new method patterns |
|||
3. **Validate functionality** after each change |
|||
4. **Monitor for any missed usage** |
|||
|
|||
## Changes to Be Made |
|||
|
|||
### Interface Consolidation |
|||
|
|||
- **Consolidate** `IPlatformServiceMixin` and `ComponentCustomProperties` interfaces |
|||
- **Eliminate duplication** by making `ComponentCustomProperties` extend |
|||
`IPlatformServiceMixin` |
|||
- **Single source of truth** for all PlatformServiceMixin methods |
|||
|
|||
### Deprecated Method Removal - PLANNED BREAKING CHANGE |
|||
|
|||
- **Remove** deprecated `$updateSettings` method from `IPlatformServiceMixin` interface |
|||
- **Remove** deprecated `$updateSettings` method from `ComponentCustomProperties` |
|||
interface |
|||
- **Remove** deprecated `$updateSettings` method implementation |
|||
- **⚠️ This will break existing functionality** |
|||
|
|||
### Code Cleanup |
|||
|
|||
- **Eliminate** duplicate method definitions |
|||
- **Remove** outdated comments about deprecated methods |
|||
- **Consolidate** interface maintenance to single location |
|||
|
|||
## Files to Be Modified |
|||
|
|||
### `src/utils/PlatformServiceMixin.ts` |
|||
|
|||
- Remove deprecated `$updateSettings` method from `IPlatformServiceMixin` interface |
|||
- Remove deprecated `$updateSettings` method from `ComponentCustomProperties` |
|||
interface |
|||
- Remove deprecated `$updateSettings` method implementation |
|||
- Make `ComponentCustomProperties` extend `IPlatformServiceMixin` for consistency |
|||
- Add comments explaining deprecated method removal |
|||
|
|||
## Proper Usage Patterns |
|||
|
|||
### Settings Management |
|||
|
|||
#### Default Settings (Global) |
|||
|
|||
```typescript |
|||
// Save to master settings table |
|||
await this.$saveSettings({ |
|||
apiServer: 'https://api.example.com', |
|||
defaultLanguage: 'en' |
|||
}); |
|||
``` |
|||
|
|||
#### User-Specific Settings |
|||
|
|||
```typescript |
|||
// Save to user-specific settings table |
|||
await this.$saveUserSettings(userDid, { |
|||
firstName: 'John', |
|||
isRegistered: true, |
|||
profileImageUrl: 'https://example.com/avatar.jpg' |
|||
}); |
|||
``` |
|||
|
|||
#### Current User Settings |
|||
|
|||
```typescript |
|||
// Automatically uses current activeDid |
|||
await this.$saveMySettings({ |
|||
firstName: 'John', |
|||
isRegistered: true |
|||
}); |
|||
``` |
|||
|
|||
### Database Operations |
|||
|
|||
#### Ultra-Concise Methods |
|||
|
|||
```typescript |
|||
// Shortest possible names for frequent operations |
|||
const contacts = await this.$contacts(); |
|||
const settings = await this.$settings(); |
|||
const result = await this.$db("SELECT * FROM table WHERE id = ?", [id]); |
|||
await this.$exec("UPDATE table SET field = ? WHERE id = ?", [value, id]); |
|||
const row = await this.$one("SELECT * FROM table WHERE id = ?", [id]); |
|||
``` |
|||
|
|||
#### Query + Mapping Combo |
|||
|
|||
```typescript |
|||
// Automatic result mapping |
|||
const users = await this.$query<User>("SELECT * FROM users WHERE active = ?", [true]); |
|||
const firstUser = await this.$first<User>("SELECT * FROM users WHERE id = ?", [id]); |
|||
``` |
|||
|
|||
#### Entity Operations |
|||
|
|||
```typescript |
|||
// High-level entity management |
|||
await this.$insertContact({ |
|||
did: 'did:example:123', |
|||
name: 'John Doe', |
|||
publicKeyBase64: 'base64key' |
|||
}); |
|||
|
|||
await this.$updateContact('did:example:123', { |
|||
name: 'John Smith' |
|||
}); |
|||
|
|||
const contact = await this.$getContact('did:example:123'); |
|||
await this.$deleteContact('did:example:123'); |
|||
``` |
|||
|
|||
## Migration Guide |
|||
|
|||
### From $updateSettings to Proper Methods |
|||
|
|||
#### Before (Deprecated - Will Break After Implementation) |
|||
|
|||
```typescript |
|||
// ❌ DEPRECATED - This will cause runtime crashes after implementation |
|||
await this.$updateSettings({ firstName: 'John' }); |
|||
await this.$updateSettings({ isRegistered: true }, userDid); |
|||
``` |
|||
|
|||
#### After (Required - After Migration) |
|||
|
|||
```typescript |
|||
// ✅ For default/global settings |
|||
await this.$saveSettings({ firstName: 'John' }); |
|||
|
|||
// ✅ For user-specific settings |
|||
await this.$saveUserSettings(userDid, { isRegistered: true }); |
|||
|
|||
// ✅ For current user (automatically uses activeDid) |
|||
await this.$saveMySettings({ firstName: 'John' }); |
|||
``` |
|||
|
|||
### Component Implementation |
|||
|
|||
#### Class Component with Mixin |
|||
|
|||
```typescript |
|||
import { Component, Vue } from 'vue-facing-decorator'; |
|||
import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin'; |
|||
|
|||
@Component({ |
|||
mixins: [PlatformServiceMixin] |
|||
}) |
|||
export default class MyComponent extends Vue { |
|||
async saveUserProfile() { |
|||
// Use the consolidated interface methods |
|||
await this.$saveUserSettings(this.activeDid, { |
|||
firstName: this.firstName, |
|||
lastName: this.lastName |
|||
}); |
|||
} |
|||
|
|||
async loadData() { |
|||
// Use ultra-concise methods |
|||
const contacts = await this.$contacts(); |
|||
const settings = await this.$settings(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### Composition API with Mixin |
|||
|
|||
```typescript |
|||
import { defineComponent } from 'vue'; |
|||
import { PlatformServiceMixin } from '@/utils/PlatformServiceMixin'; |
|||
|
|||
export default defineComponent({ |
|||
mixins: [PlatformServiceMixin], |
|||
async setup() { |
|||
// Methods available through mixin |
|||
const saveSettings = async (changes) => { |
|||
return await this.$saveSettings(changes); |
|||
}; |
|||
|
|||
return { |
|||
saveSettings |
|||
}; |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
## Impact |
|||
|
|||
### Benefits |
|||
|
|||
- **Eliminates interface duplication** - single source of truth |
|||
- **Forces proper method usage** - no more deprecated `$updateSettings` |
|||
- **Improves maintainability** - changes only needed in one place |
|||
- **Enhances type safety** - consistent interfaces across all contexts |
|||
- **Better developer experience** - clear method patterns and documentation |
|||
|
|||
### Breaking Changes - CRITICAL |
|||
|
|||
- **`$updateSettings` method no longer available** - will cause runtime crashes |
|||
- **Interface consolidation** - ensures consistent method availability |
|||
- **App will not work** until migration is complete |
|||
|
|||
### Migration Required |
|||
|
|||
- **All components using `$updateSettings`** must be updated to use proper settings |
|||
methods |
|||
- **Systematic migration** needed across multiple components |
|||
- **Breaking change** will be introduced after implementation |
|||
|
|||
## Security Audit Checklist |
|||
|
|||
- ✅ **Input Validation**: All database methods include proper parameter validation |
|||
- ✅ **SQL Injection Protection**: Parameterized queries used throughout |
|||
- ✅ **Access Control**: User-specific settings properly isolated by DID |
|||
- ✅ **Error Handling**: Comprehensive error logging and graceful fallbacks |
|||
- ✅ **Type Safety**: Full TypeScript support prevents invalid data types |
|||
- ✅ **Transaction Management**: Automatic rollback on database errors |
|||
|
|||
## Performance Optimizations |
|||
|
|||
### Caching Strategy |
|||
|
|||
- **NO CACHING**: Settings loaded fresh every time (no stale data) |
|||
- **NO CACHING**: Contacts loaded fresh every time (no stale data) |
|||
- **NO CACHING**: All database operations return fresh data |
|||
- Memory-efficient data structures |
|||
|
|||
### Database Operations |
|||
|
|||
- Ultra-concise method names reduce boilerplate |
|||
- Automatic transaction management |
|||
- Optimized SQL queries with proper indexing |
|||
|
|||
### Resource Management |
|||
|
|||
- **NO WeakMap-based caching** - all caching code is commented out |
|||
- **NO cache invalidation** - not needed since nothing is cached |
|||
- **NO memory leaks from caching** - because there is no caching |
|||
- Efficient component lifecycle management |
|||
|
|||
### Caching Confusion Clarification |
|||
|
|||
**Important Note**: There are several references to "caching" throughout the codebase |
|||
that are **misleading and incorrect**: |
|||
|
|||
#### What the Documentation Claims vs. Reality |
|||
|
|||
| Claimed Feature | Actual Reality | |
|||
|----------------|----------------| |
|||
| "Smart caching layer with TTL" | ❌ **NO CACHING IMPLEMENTED** | |
|||
| "WeakMap-based caching prevents memory leaks" | ❌ **ALL CACHING CODE COMMENTED OUT** | |
|||
| "Cached database operations" | ❌ **EVERYTHING LOADED FRESH** | |
|||
| "Settings shortcuts for ultra-frequent update patterns" | ❌ **NO CACHING, JUST CONVENIENCE METHODS** | |
|||
|
|||
#### Evidence of No Caching |
|||
|
|||
1. **All caching code is commented out** in `PlatformServiceMixin.ts` |
|||
2. **Settings methods explicitly state** "WITHOUT caching" in comments |
|||
3. **Contacts method explicitly states** "always fresh" in comments |
|||
4. **No cache invalidation logic** exists |
|||
5. **No TTL management** exists |
|||
|
|||
#### Why This Confusion Exists |
|||
|
|||
The caching system was **planned and designed** but **never implemented**. The |
|||
documentation and comments reflect the original design intent, not the current |
|||
reality. This is a case where the documentation is ahead of the implementation. |
|||
|
|||
## Testing Considerations |
|||
|
|||
### Interface Testing |
|||
|
|||
- All methods should be tested through the consolidated interface |
|||
- Mock PlatformService for unit testing |
|||
- Integration tests for database operations |
|||
|
|||
### Migration Testing |
|||
|
|||
- Verify deprecated methods are no longer accessible |
|||
- Test new method signatures work correctly |
|||
- Ensure backward compatibility for existing functionality |
|||
|
|||
### Performance Testing |
|||
|
|||
- Monitor database query performance |
|||
- Verify caching behavior works as expected |
|||
- Test memory usage patterns |
|||
|
|||
## Next Steps - IMPLEMENTATION PLAN |
|||
|
|||
1. **Plan migration strategy** - Systematic approach to updating components |
|||
2. **Execute component migration** - Update all affected components |
|||
3. **Implement interface consolidation** - Remove deprecated method and consolidate interfaces |
|||
4. **Validate functionality** - Ensure all settings operations work correctly |
|||
5. **Update documentation** - Reflect final state after implementation |
|||
|
|||
## Conclusion |
|||
|
|||
The planned PlatformServiceMixin interface consolidation will provide: |
|||
|
|||
- **Single source of truth** for all mixin methods |
|||
- **Elimination of deprecated methods** to prevent confusion |
|||
- **Consistent interface** across all usage contexts |
|||
- **Improved maintainability** and type safety |
|||
- **Better developer experience** with clear method patterns |
|||
|
|||
**⚠️ CRITICAL**: This consolidation will introduce a breaking change that requires |
|||
careful planning and execution. The app will not work after implementation until: |
|||
|
|||
1. **All components are migrated** to use the new methods, or |
|||
2. **The deprecated method is restored** temporarily during migration |
|||
|
|||
The fact that `ComponentCustomProperties` is only used for TypeScript support |
|||
validates our approach - we're consolidating interfaces that serve different |
|||
purposes (runtime vs. TypeScript support) while eliminating duplication. |
|||
|
|||
**Status**: Planning phase - ready for implementation |
|||
**Priority**: High - requires careful migration planning |
|||
**Dependencies**: Component updates required before breaking change |
|||
**Stakeholders**: Development team, QA team |
@ -0,0 +1,28 @@ |
|||
#!/bin/bash |
|||
|
|||
# CI check script to ensure no new $updateSettings usage is introduced |
|||
# This script will fail CI if any $updateSettings calls are found |
|||
|
|||
set -e |
|||
|
|||
echo "🔍 Checking for deprecated \$updateSettings usage..." |
|||
|
|||
# Search for $updateSettings usage in source files |
|||
USAGE_COUNT=$(grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" | wc -l) |
|||
|
|||
if [ "$USAGE_COUNT" -gt 0 ]; then |
|||
echo "❌ Found $USAGE_COUNT usage(s) of deprecated \$updateSettings method:" |
|||
echo "" |
|||
grep -r "\$updateSettings" src/ --include="*.vue" --include="*.ts" --include="*.js" -n |
|||
echo "" |
|||
echo "⚠️ Migration required:" |
|||
echo " - For global settings: use \$saveSettings(changes)" |
|||
echo " - For user-specific settings: use \$saveUserSettings(did, changes)" |
|||
echo " - For current user settings: use \$saveMySettings(changes)" |
|||
echo "" |
|||
echo "Run 'node scripts/migrate-update-settings.js' for migration guidance." |
|||
exit 1 |
|||
else |
|||
echo "✅ No \$updateSettings usage found!" |
|||
exit 0 |
|||
fi |
@ -0,0 +1,110 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
/** |
|||
* Migration script to replace deprecated $updateSettings calls |
|||
* with the appropriate new methods ($saveSettings, $saveUserSettings, $saveMySettings) |
|||
* |
|||
* Usage: node scripts/migrate-update-settings.js |
|||
* |
|||
* This script will: |
|||
* 1. Find all files containing $updateSettings calls |
|||
* 2. Show the migration suggestions for each call |
|||
* 3. Optionally perform the replacements |
|||
*/ |
|||
|
|||
const fs = require('fs'); |
|||
const path = require('path'); |
|||
const glob = require('glob'); |
|||
|
|||
// Migration patterns
|
|||
const MIGRATION_PATTERNS = [ |
|||
{ |
|||
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*\)/g, |
|||
replacement: '$saveMySettings($1)', |
|||
description: 'Single parameter (changes only) -> $saveMySettings' |
|||
}, |
|||
{ |
|||
pattern: /\$updateSettings\(\s*(\{[^}]*\})\s*,\s*([^)]+)\s*\)/g, |
|||
replacement: '$saveUserSettings($2, $1)', |
|||
description: 'Two parameters (changes, did) -> $saveUserSettings(did, changes)' |
|||
} |
|||
]; |
|||
|
|||
// Find all Vue and TypeScript files
|
|||
function findFiles() { |
|||
const patterns = [ |
|||
'src/**/*.vue', |
|||
'src/**/*.ts', |
|||
'src/**/*.js' |
|||
]; |
|||
|
|||
let files = []; |
|||
patterns.forEach(pattern => { |
|||
files = files.concat(glob.sync(pattern, { ignore: ['node_modules/**', 'dist/**'] })); |
|||
}); |
|||
|
|||
return files; |
|||
} |
|||
|
|||
// Analyze a file for $updateSettings usage
|
|||
function analyzeFile(filePath) { |
|||
const content = fs.readFileSync(filePath, 'utf8'); |
|||
const lines = content.split('\n'); |
|||
const usages = []; |
|||
|
|||
lines.forEach((line, index) => { |
|||
if (line.includes('$updateSettings')) { |
|||
usages.push({ |
|||
line: index + 1, |
|||
content: line.trim(), |
|||
file: filePath |
|||
}); |
|||
console.log(`\n${filePath}:${index + 1}`); |
|||
console.log(` ${line.trim()}`); |
|||
|
|||
// Show migration suggestion
|
|||
MIGRATION_PATTERNS.forEach(pattern => { |
|||
if (pattern.pattern.test(line)) { |
|||
const replacement = line.replace(pattern.pattern, pattern.replacement); |
|||
console.log(` → ${replacement.trim()}`); |
|||
console.log(` ${pattern.description}`); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return usages; |
|||
} |
|||
|
|||
// Main execution
|
|||
function main() { |
|||
console.log('🔍 Finding files with $updateSettings usage...\n'); |
|||
|
|||
const files = findFiles(); |
|||
let totalUsages = 0; |
|||
|
|||
files.forEach(file => { |
|||
const usages = analyzeFile(file); |
|||
totalUsages += usages.length; |
|||
}); |
|||
|
|||
console.log(`\n📊 Summary:`); |
|||
console.log(` Files scanned: ${files.length}`); |
|||
console.log(` Total usages: ${totalUsages}`); |
|||
|
|||
if (totalUsages > 0) { |
|||
console.log(`\n📝 Migration Guide:`); |
|||
console.log(` 1. For global/default settings: use $saveSettings(changes)`); |
|||
console.log(` 2. For user-specific settings: use $saveUserSettings(did, changes)`); |
|||
console.log(` 3. For current user settings: use $saveMySettings(changes)`); |
|||
console.log(`\n⚠️ Note: $updateSettings is deprecated and will be removed in a future version.`); |
|||
} else { |
|||
console.log(`\n✅ No $updateSettings usage found!`); |
|||
} |
|||
} |
|||
|
|||
if (require.main === module) { |
|||
main(); |
|||
} |
|||
|
|||
module.exports = { findFiles, analyzeFile, MIGRATION_PATTERNS }; |
Loading…
Reference in new issue