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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"root": true,
"env": {
"browser": true,
"es2021": true,
"webextensions": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"plugins": [
"@typescript-eslint",
"react",
"react-hooks"
],
"rules": {
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"no-console": ["warn", { "allow": ["warn", "error"] }]
},
"settings": {
"react": {
"version": "detect"
}
},
"ignorePatterns": [
"dist/",
"node_modules/",
"*.config.js",
"*.config.ts"
]
}

9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,15 @@ npm run build
| `Alt+D` | Toggle Drawing | Enable/disable drawing mode |
| `Alt+S` | Toggle Sidebar | Open/close the side panel |
| `Alt+A` | Open Annotations | Jump to annotations tab |
| `Alt+Shift+A` | Toggle Annotations | Enable/disable annotation button |
| `Shift+?` | Show Shortcuts | Display keyboard shortcuts help |
| `Ctrl+Z` / `Cmd+Z` | Undo | Undo last action (in drawing mode) |
| `Ctrl+Y` / `Cmd+Shift+Z` | Redo | Redo last action (in drawing mode) |
| `Escape` | Close/Exit | Close modal or exit drawing mode |

*On Mac, use `Option` instead of `Alt`*
*On Mac, use `Option` instead of `Alt` and `Cmd` instead of `Ctrl`*

**Tip:** Press `Shift+?` in the popup or sidepanel to see all available shortcuts!

## 📖 Usage Guide

Expand Down
111 changes: 111 additions & 0 deletions docs/DEPENDENCY_UPGRADE_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Dependency Upgrade Plan

This document outlines the planned dependency upgrades for post-launch maintenance.

## Current Status

### Production Dependencies
- `@synonymdev/pubky`: `^0.5.4` (pinned)
- `dompurify`: `^3.3.1`
- `react`: `^18.3.1`
- `react-dom`: `^18.3.1`

### Dev Dependencies
- `vite`: `^5.3.3`
- `vitest`: `^1.3.1`
- `typescript`: `^5.5.3`
- `@playwright/test`: `^1.40.0`

## Upgrade Priorities

### High Priority (Post-Launch)

1. **@synonymdev/pubky** (when stable)
- Current: `^0.5.4`
- Target: Latest stable (check changelog for breaking changes)
- Risk: Medium - Core SDK, test thoroughly
- Timeline: After 1-2 months of stable production use

2. **React** (when 19.x stable)
- Current: `^18.3.1`
- Target: `^19.0.0` (when released)
- Risk: Medium - May require code changes
- Timeline: Wait for ecosystem adoption

### Medium Priority (Quarterly)

3. **Vite** (when 6.x stable)
- Current: `^5.3.3`
- Target: `^6.0.0` (when released)
- Risk: Medium - Build tool, test build process
- Timeline: After Vite 6 stable release

4. **TypeScript** (quarterly)
- Current: `^5.5.3`
- Target: Latest 5.x (avoid 6.x until stable)
- Risk: Low - Usually backward compatible
- Timeline: Every 3 months

5. **Playwright** (quarterly)
- Current: `^1.40.0`
- Target: Latest 1.x
- Risk: Low - Test framework, update tests if needed
- Timeline: Every 3 months

### Low Priority (As Needed)

6. **DOMPurify** (security updates)
- Current: `^3.3.1`
- Target: Latest 3.x
- Risk: Low - Security library, patch updates only
- Timeline: When security patches released

7. **Vitest** (quarterly)
- Current: `^1.3.1`
- Target: Latest 1.x
- Risk: Low - Test framework
- Timeline: Every 3 months

## Upgrade Process

1. **Create feature branch**: `chore/upgrade-{package}-{version}`
2. **Update package.json**: Change version range
3. **Run `npm install`**: Install new version
4. **Run `npm run typecheck`**: Check for type errors
5. **Run `npm run lint`**: Check for linting issues
6. **Run `npm test`**: Run unit tests
7. **Run `npm run test:e2e`**: Run E2E tests
8. **Manual testing**: Test critical user flows
9. **Update CHANGELOG.md**: Document upgrade
10. **Create PR**: Get review before merging

## Breaking Changes Tracking

### @synonymdev/pubky
- Monitor: https://github.com/synonymdev/pubky/releases
- Watch for: API changes, Client constructor changes, auth flow changes

### React 19
- Monitor: https://react.dev/blog
- Watch for: Hook changes, component API changes, concurrent features

### Vite 6
- Monitor: https://vitejs.dev/blog
- Watch for: Config changes, plugin API changes, build output changes

## Security Updates

For security-related updates:
1. Create hotfix branch immediately
2. Test critical paths only
3. Deploy to production ASAP
4. Full testing in follow-up PR

## Notes

- Always test in development environment first
- Keep backup of working package-lock.json
- Document any breaking changes in code comments
- Update type definitions if needed
- Check peer dependency requirements

10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"test:ui": "vitest --ui",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:errors": "node run-e2e-with-error-capture.js"
"test:e2e:errors": "node run-e2e-with-error-capture.js",
"typecheck": "tsc --noEmit",
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix"
},
"dependencies": {
"@synonymdev/pubky": "^0.5.4",
Expand All @@ -41,10 +44,15 @@
"@types/qrcode": "^1.5.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.49.0",
"@typescript-eslint/parser": "^8.49.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitest/coverage-v8": "^1.3.1",
"@vitest/ui": "^1.3.1",
"autoprefixer": "^10.4.19",
"eslint": "^9.39.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"jsdom": "^24.0.0",
"postcss": "^8.4.39",
"tailwindcss": "^3.4.4",
Expand Down
114 changes: 114 additions & 0 deletions src/popup/components/ProfileEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { storage, ProfileData } from '../../utils/storage';
import { profileManager } from '../../utils/profile-manager';
import { logger } from '../../utils/logger';
import { validateProfile, VALIDATION_LIMITS } from '../../utils/validation';
import { exportRecoveryFile, validatePassphrase } from '../../utils/recovery-file';
import ProgressBar from './ProgressBar';
import ImageCropper from './ImageCropper';

Expand Down Expand Up @@ -538,8 +539,121 @@ export function ProfileEditor() {
<p className="text-xs text-gray-400">
* Your profile will be saved as profile.json and a generated index.html on your homeserver
</p>

{/* Recovery File Export */}
<div className="border-t border-gray-700 pt-4 mt-4">
<h3 className="text-sm font-medium text-gray-300 mb-2">Key Backup</h3>
<p className="text-xs text-gray-400 mb-3">
Export a recovery file to backup your keys. Store it securely - you'll need it if you lose access to your device.
</p>
<button
onClick={() => setShowRecoveryModal(true)}
className="w-full px-4 py-2 bg-yellow-600/20 hover:bg-yellow-600/30 text-yellow-400 border border-yellow-600/50 rounded-lg transition focus:outline-none focus:ring-2 focus:ring-yellow-500"
Comment on lines +549 to +551

Choose a reason for hiding this comment

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

P0 Badge Define recovery modal state before use

The new recovery export UI calls setShowRecoveryModal here and later reads showRecoveryModal, recoveryPassphrase, recoveryPassphraseConfirm, recoveryError, and isExportingRecovery, but the component never declares these state hooks (only name/bio/upload state is initialized at the top). This leaves the component using undefined identifiers, so the TypeScript build will fail (and at runtime would throw ReferenceError) as soon as the file is imported, blocking the profile editor and the new recovery flow entirely.

Useful? React with 👍 / 👎.

aria-label="Export recovery file"
>
🔐 Export Recovery File
</button>
</div>
</div>

{/* Recovery File Modal */}
{showRecoveryModal && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-[#1F1F1F] border border-[#3F3F3F] rounded-lg p-6 max-w-md w-full">
<h3 className="text-lg font-bold text-white mb-4">Export Recovery File</h3>
<p className="text-sm text-gray-400 mb-4">
Enter a strong passphrase to encrypt your recovery file. You'll need this passphrase to restore your keys.
</p>

<div className="space-y-3">
<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Passphrase
</label>
<input
type="password"
value={recoveryPassphrase}
onChange={(e) => {
setRecoveryPassphrase(e.target.value);
setRecoveryError(null);
}}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-yellow-500"
placeholder="Enter passphrase (min 8 characters)"
/>
</div>

<div>
<label className="block text-sm font-medium text-gray-300 mb-1">
Confirm Passphrase
</label>
<input
type="password"
value={recoveryPassphraseConfirm}
onChange={(e) => {
setRecoveryPassphraseConfirm(e.target.value);
setRecoveryError(null);
}}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-yellow-500"
placeholder="Confirm passphrase"
/>
</div>

{recoveryError && (
<div className="text-sm text-red-400">{recoveryError}</div>
)}

<div className="flex gap-2 mt-4">
<button
onClick={async () => {
setRecoveryError(null);

// Validate passphrase
const validation = validatePassphrase(recoveryPassphrase);
if (!validation.isValid) {
setRecoveryError(validation.error || 'Invalid passphrase');
return;
}

if (recoveryPassphrase !== recoveryPassphraseConfirm) {
setRecoveryError('Passphrases do not match');
return;
}

try {
setIsExportingRecovery(true);
await exportRecoveryFile(recoveryPassphrase);
showMessage('success', 'Recovery file exported successfully');
setShowRecoveryModal(false);
setRecoveryPassphrase('');
setRecoveryPassphraseConfirm('');
} catch (error) {
setRecoveryError((error as Error).message || 'Failed to export recovery file');
} finally {
setIsExportingRecovery(false);
}
}}
disabled={isExportingRecovery || !recoveryPassphrase || !recoveryPassphraseConfirm}
className="flex-1 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:bg-gray-600 text-white rounded-lg transition focus:outline-none focus:ring-2 focus:ring-yellow-500"
>
{isExportingRecovery ? 'Exporting...' : 'Export'}
</button>
<button
onClick={() => {
setShowRecoveryModal(false);
setRecoveryPassphrase('');
setRecoveryPassphraseConfirm('');
setRecoveryError(null);
}}
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition focus:outline-none focus:ring-2 focus:ring-gray-500"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}

{/* Image Cropper Modal */}
{showCropper && imageFileToCrop && (
<ImageCropper
Expand Down
Loading
Loading