vault backup: 2026-09-26 11:26:54
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
|
|
||||||
/.obsidian/workspace.json
|
/.obsidian/workspace.json
|
||||||
|
/.obsidian/plugins/*
|
||||||
|
/.obsidian/workspace.json
|
||||||
Vendored
+6
-1
@@ -1 +1,6 @@
|
|||||||
{}
|
{
|
||||||
|
"cssTheme": "",
|
||||||
|
"accentColor": "#16ac20",
|
||||||
|
"theme": "system",
|
||||||
|
"interfaceFontFamily": ""
|
||||||
|
}
|
||||||
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
[
|
[
|
||||||
"obsidian-git",
|
"obsidian-git",
|
||||||
"tasknotes",
|
"tasknotes",
|
||||||
"advanced-canvas"
|
"advanced-canvas",
|
||||||
|
"awesome-format-bar"
|
||||||
]
|
]
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"settings": {
|
|
||||||
"migrated": 6,
|
|
||||||
"iconPacksPath": ".obsidian/icons",
|
|
||||||
"fontSize": 16,
|
|
||||||
"emojiStyle": "native",
|
|
||||||
"iconColor": null,
|
|
||||||
"recentlyUsedIcons": [
|
|
||||||
"LiCheck",
|
|
||||||
"LiTasknotesSimple",
|
|
||||||
"LiBookMarked",
|
|
||||||
"LiInfo"
|
|
||||||
],
|
|
||||||
"recentlyUsedIconsSize": 5,
|
|
||||||
"rules": [],
|
|
||||||
"extraMargin": {
|
|
||||||
"top": 0,
|
|
||||||
"right": 4,
|
|
||||||
"bottom": 0,
|
|
||||||
"left": 0
|
|
||||||
},
|
|
||||||
"iconInTabsEnabled": false,
|
|
||||||
"iconInTitleEnabled": false,
|
|
||||||
"iconInTitlePosition": "above",
|
|
||||||
"iconInFrontmatterEnabled": false,
|
|
||||||
"iconInFrontmatterFieldName": "icon",
|
|
||||||
"iconColorInFrontmatterFieldName": "iconColor",
|
|
||||||
"iconsBackgroundCheckEnabled": false,
|
|
||||||
"iconsInNotesEnabled": true,
|
|
||||||
"iconsInLinksEnabled": true,
|
|
||||||
"iconIdentifier": ":",
|
|
||||||
"lucideIconPackType": "native",
|
|
||||||
"debugMode": false,
|
|
||||||
"useInternalPlugins": false
|
|
||||||
},
|
|
||||||
"00 About": "LiInfo",
|
|
||||||
"01 Project docs": "LiBookMarked",
|
|
||||||
"TaskNotes": "LiCheck"
|
|
||||||
}
|
|
||||||
-7158
File diff suppressed because one or more lines are too long
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "obsidian-icon-folder",
|
|
||||||
"name": "Iconize",
|
|
||||||
"version": "2.14.7",
|
|
||||||
"minAppVersion": "0.9.12",
|
|
||||||
"description": "Add icons to anything you desire in Obsidian, including files, folders, and text.",
|
|
||||||
"author": "Florian Woelki",
|
|
||||||
"authorUrl": "https://florianwoelki.com/",
|
|
||||||
"isDesktopOnly": false
|
|
||||||
}
|
|
||||||
-120
@@ -1,120 +0,0 @@
|
|||||||
.iconize-inline-title-wrapper {
|
|
||||||
width: var(--line-width);
|
|
||||||
max-width: var(--max-width);
|
|
||||||
margin-inline: var(--content-margin);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-title-icon {
|
|
||||||
max-width: var(--max-width);
|
|
||||||
margin-right: var(--size-4-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-icon-in-link {
|
|
||||||
transform: translateY(20%);
|
|
||||||
margin-right: var(--size-2-2);
|
|
||||||
display: inline-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-icon {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
margin: 0px 4px 0px 0px;
|
|
||||||
display: flex;
|
|
||||||
align-self: center;
|
|
||||||
margin: auto 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-folder-title,
|
|
||||||
.nav-file-title {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-setting input[type='color'] {
|
|
||||||
margin: 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-modal.prompt-results {
|
|
||||||
margin: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt .iconize-subheadline {
|
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: gray;
|
|
||||||
grid-column-start: 1;
|
|
||||||
grid-column-end: 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.iconize-modal.prompt-results {
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
.prompt .iconize-subheadline {
|
|
||||||
grid-column-end: 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-modal.prompt-results .suggestion-item {
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
padding: 16px 8px;
|
|
||||||
line-break: auto;
|
|
||||||
word-break: break-word;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-modal.prompt-results .suggestion-item.suggestion-item__center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-icon-preview {
|
|
||||||
font-size: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-icon-preview img {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-icon-preview svg {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
color: currentColor;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-dragover {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-dragover-el {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
color: var(--text-normal);
|
|
||||||
background-color: var(--background-secondary-alt);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom rule modal. */
|
|
||||||
.iconize-custom-modal .modal-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconize-custom-modal .modal-content input {
|
|
||||||
width: 100%;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
Vendored
+21
-17
@@ -4,21 +4,24 @@
|
|||||||
"type": "split",
|
"type": "split",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "c2be3e9a062c0b56",
|
"id": "6cfedf15c65a27df",
|
||||||
"type": "tabs",
|
"type": "tabs",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "87c3a13251758aec",
|
"id": "060120ed5e7b1b64",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "split-diff-view",
|
"type": "canvas",
|
||||||
"state": {
|
"state": {
|
||||||
"aFile": ".gitignore",
|
"file": "00 About/Overview diagram.canvas",
|
||||||
"bFile": ".gitignore",
|
"viewState": {
|
||||||
"aRef": ""
|
"x": -630,
|
||||||
|
"y": -340,
|
||||||
|
"zoom": -0.5081109222000615
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"icon": "diff",
|
"icon": "lucide-layout-dashboard",
|
||||||
"title": "Diff: .gitignore (Working Tree)"
|
"title": "Overview diagram"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -182,34 +185,35 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 300
|
"width": 323.5
|
||||||
},
|
},
|
||||||
"left-ribbon": {
|
"left-ribbon": {
|
||||||
"hiddenItems": {
|
"hiddenItems": {
|
||||||
"switcher:Open quick switcher": false,
|
"switcher:Open quick switcher": true,
|
||||||
"graph:Open graph view": false,
|
|
||||||
"canvas:Create new canvas": false,
|
"canvas:Create new canvas": false,
|
||||||
"daily-notes:Open today's daily note": false,
|
"daily-notes:Open today's daily note": true,
|
||||||
"templates:Insert template": false,
|
"templates:Insert template": false,
|
||||||
"command-palette:Open command palette": false,
|
"command-palette:Open command palette": false,
|
||||||
"bases:Create new base": false,
|
"bases:Create new base": true,
|
||||||
"obsidian-git:Open Git source control": false,
|
"obsidian-git:Open Git source control": false,
|
||||||
"tasknotes:Open mini calendar view": true,
|
"tasknotes:Open mini calendar view": true,
|
||||||
"tasknotes:Open calendar view": true,
|
"tasknotes:Open calendar view": true,
|
||||||
"tasknotes:Open tasks view": true,
|
"tasknotes:Open tasks view": false,
|
||||||
"tasknotes:Open agenda view": true,
|
"tasknotes:Open agenda view": true,
|
||||||
"tasknotes:Open Kanban board": false,
|
"tasknotes:Open Kanban board": false,
|
||||||
|
"graph:Open graph view": false,
|
||||||
"tasknotes:Open Pomodoro timer": true,
|
"tasknotes:Open Pomodoro timer": true,
|
||||||
"tasknotes:Open Pomodoro statistics": true,
|
"tasknotes:Open Pomodoro statistics": true,
|
||||||
"tasknotes:Create new task": false
|
"tasknotes:Create new task": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "87c3a13251758aec",
|
"active": "060120ed5e7b1b64",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"00 About/Requirements.md",
|
||||||
"00 About/Overview diagram.canvas",
|
"00 About/Overview diagram.canvas",
|
||||||
|
"TaskNotes/Start Here.md",
|
||||||
"Untitled Document",
|
"Untitled Document",
|
||||||
"TaskNotes/Views/kanban-default.base",
|
"TaskNotes/Views/kanban-default.base",
|
||||||
"TaskNotes/Start Here.md",
|
|
||||||
"TaskNotes/Views/calendar-default.base"
|
"TaskNotes/Views/calendar-default.base"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -51,51 +51,10 @@
|
|||||||
"styleAttributes":{"textAlign":"center"},
|
"styleAttributes":{"textAlign":"center"},
|
||||||
"x":-760,
|
"x":-760,
|
||||||
"y":-440,
|
"y":-440,
|
||||||
"width":180,
|
"width":220,
|
||||||
"height":270,
|
"height":270,
|
||||||
"color":"6"
|
"color":"6"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id":"0648cf705ffedc6f",
|
|
||||||
"type":"text",
|
|
||||||
"text":"Overview board:\n- pregled nad vsemi naročili\n",
|
|
||||||
"styleAttributes":{"textAlign":"center"},
|
|
||||||
"x":-440,
|
|
||||||
"y":-300,
|
|
||||||
"width":260,
|
|
||||||
"height":100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":"5e0af7712a4944dd",
|
|
||||||
"type":"text",
|
|
||||||
"text":"Admin",
|
|
||||||
"styleAttributes":{"textAlign":"center"},
|
|
||||||
"x":-220,
|
|
||||||
"y":-440,
|
|
||||||
"width":140,
|
|
||||||
"height":60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":"909284a7d8e1f746",
|
|
||||||
"type":"text",
|
|
||||||
"text":"QR code scan\n",
|
|
||||||
"styleAttributes":{"textAlign":"center"},
|
|
||||||
"x":-120,
|
|
||||||
"y":-300,
|
|
||||||
"width":260,
|
|
||||||
"height":100
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id":"9e2bb4fd16870db1",
|
|
||||||
"type":"text",
|
|
||||||
"text":"Prijava",
|
|
||||||
"styleAttributes":{"textAlign":"center"},
|
|
||||||
"x":-220,
|
|
||||||
"y":-600,
|
|
||||||
"width":140,
|
|
||||||
"height":60,
|
|
||||||
"color":"2"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id":"02dbc1e4176bfdb2",
|
"id":"02dbc1e4176bfdb2",
|
||||||
"type":"text",
|
"type":"text",
|
||||||
@@ -126,6 +85,47 @@
|
|||||||
"width":220,
|
"width":220,
|
||||||
"height":60,
|
"height":60,
|
||||||
"color":"2"
|
"color":"2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"0648cf705ffedc6f",
|
||||||
|
"type":"text",
|
||||||
|
"text":"Overview board:\n- pregled nad vsemi naročili\n",
|
||||||
|
"styleAttributes":{"textAlign":"center"},
|
||||||
|
"x":-440,
|
||||||
|
"y":-300,
|
||||||
|
"width":260,
|
||||||
|
"height":160
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"5e0af7712a4944dd",
|
||||||
|
"type":"text",
|
||||||
|
"text":"Admin",
|
||||||
|
"styleAttributes":{"textAlign":"center"},
|
||||||
|
"x":-220,
|
||||||
|
"y":-440,
|
||||||
|
"width":140,
|
||||||
|
"height":60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"909284a7d8e1f746",
|
||||||
|
"type":"text",
|
||||||
|
"text":"QR code scan\n",
|
||||||
|
"styleAttributes":{"textAlign":"center"},
|
||||||
|
"x":-120,
|
||||||
|
"y":-300,
|
||||||
|
"width":260,
|
||||||
|
"height":100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id":"9e2bb4fd16870db1",
|
||||||
|
"type":"text",
|
||||||
|
"text":"Prijava",
|
||||||
|
"styleAttributes":{"textAlign":"center"},
|
||||||
|
"x":-220,
|
||||||
|
"y":-600,
|
||||||
|
"width":140,
|
||||||
|
"height":60,
|
||||||
|
"color":"2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"edges":[
|
"edges":[
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
### 📱 PERSON A — Web App (Customer-Facing)
|
||||||
|
|
||||||
|
#### Functional Requirements
|
||||||
|
|
||||||
|
- **WEB-FR-1**: User registration (email + password) with email verification
|
||||||
|
- **WEB-FR-2**: Login / logout
|
||||||
|
- **WEB-FR-3**: Password reset flow
|
||||||
|
- **WEB-FR-4**: Display the event's card offering — name, description, price, expiration date
|
||||||
|
- **WEB-FR-5**: Checkout flow via Stripe (EUR)
|
||||||
|
- _Open item: single card per order, or multiple?_
|
||||||
|
- **WEB-FR-6**: On confirmed payment (via Stripe webhook), trigger card/QR generation (calls shared backend)
|
||||||
|
- **WEB-FR-7**: Send confirmation email with QR code attached/embedded (via transactional email service)
|
||||||
|
- **WEB-FR-8**: "My Cards" account page — shows purchase history, card status (unused/used/expired), QR re-download button
|
||||||
|
- **WEB-FR-9**: Basic account settings page (update email/password)
|
||||||
|
|
||||||
|
#### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **WEB-NFR-1**: HTTPS only
|
||||||
|
- **WEB-NFR-2**: GDPR-compliant — privacy policy, consent checkbox at registration, data retention/erasure process
|
||||||
|
- **WEB-NFR-3**: No raw card/payment data stored — Stripe handles PCI compliance, you only store payment references
|
||||||
|
- **WEB-NFR-4**: Responsive design (mobile browser support, since users will pull up emails on phones too, though the QR itself is scanned by the admin's phone, not theirs)
|
||||||
|
|
||||||
|
#### Open Items for This Side
|
||||||
|
|
||||||
|
1. Multi-card orders — yes/no, and if yes, does each card need a separate recipient email field?
|
||||||
|
2. Refund/cancellation flow — in scope for v1 or not?
|
||||||
|
3. Any requirement to resend the QR email if a user loses it (vs. just downloading from account page)?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📷 PERSON B — Android Scanner App (Admin-Facing)
|
||||||
|
|
||||||
|
#### Functional Requirements
|
||||||
|
|
||||||
|
- **APP-FR-1**: Single admin login (one hardcoded/managed admin role — no multi-account complexity)
|
||||||
|
- **APP-FR-2**: Camera-based QR scanning (use a standard library — e.g., ML Kit or ZXing)
|
||||||
|
- **APP-FR-3**: On scan, send token to backend validation endpoint
|
||||||
|
- **APP-FR-4**: Display result clearly and immediately:
|
||||||
|
- ✅ **Valid** → show card owner name + card type, mark as used
|
||||||
|
- ❌ **Already Used** → show when it was originally used
|
||||||
|
- ❌ **Expired** → show expiration date
|
||||||
|
- ❌ **Not Found** → invalid/forged code
|
||||||
|
- **APP-FR-5**: Local on-device scan log (timestamp + result) for admin's own reference during the event
|
||||||
|
- **APP-FR-6**: Manual entry fallback (in case camera/QR fails to scan — type in code manually)
|
||||||
|
|
||||||
|
#### Non-Functional Requirements
|
||||||
|
|
||||||
|
- **APP-NFR-1**: Requires internet connectivity to validate (confirm: acceptable, or do you need offline fallback?)
|
||||||
|
- **APP-NFR-2**: Fast response time — scan-to-result should feel instant (sub-1s ideally) since there may be a line of people
|
||||||
|
- **APP-NFR-3**: Simple, high-contrast UI readable in bright outdoor daylight or dark event lighting
|
||||||
|
- **APP-NFR-4**: Since it's single-admin/single-device, no need to handle concurrent scan conflicts — backend validation is still atomic, but app itself doesn't need sync logic
|
||||||
|
|
||||||
|
#### Open Items for This Side
|
||||||
|
|
||||||
|
1. **Offline fallback** — if there's no signal at the venue, what happens? Worth deciding now since it affects architecture (native online-only call vs. local cache + sync).
|
||||||
|
2. Does the admin need to _undo_ an accidental valid scan (e.g., mis-scan, wrong card)?
|
||||||
|
3. Any requirement to see a running count (e.g., "142 checked in") during the event?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔗 SHARED — Backend Contract (Both of you depend on this)
|
||||||
|
|
||||||
|
This is the glue between your two apps — worth agreeing on before you start building independently, since a mismatch here is the most likely source of integration pain later.
|
||||||
|
|
||||||
|
- **API-1**: `POST /api/orders` — creates order, integrates Stripe checkout
|
||||||
|
- **API-2**: Stripe webhook handler — on payment success, generates Card record + signed QR token, triggers email
|
||||||
|
- **API-3**: `POST /api/cards/validate` — takes scanned token, returns `{status: valid|used|expired|not_found, owner_name, card_type, used_at?}`, atomically updates status if valid
|
||||||
|
- **API-4**: `GET /api/users/me/cards` — for the web app's "My Cards" page
|
||||||
|
- **API-5**: Shared **Card** data model: `id, unique_token (signed), user_id, card_type, status, purchased_at, expires_at, used_at`
|
||||||
|
- **API-6**: Auth — separate token/role for admin scanner vs. customer web sessions
|
||||||
|
|
||||||
|
**Recommendation:** whoever owns the backend (or whoever's building it jointly) should nail down the exact JSON shape of `API-3`'s response first, since that's the contract Person B's UI directly renders.
|
||||||
Reference in New Issue
Block a user