diff --git a/frontend/src/App.scss b/frontend/src/App.scss index e6bed5b..9436d8b 100644 --- a/frontend/src/App.scss +++ b/frontend/src/App.scss @@ -10,15 +10,14 @@ /* Excalidraw Context Menu Styling Overrides */ :global(.excalidraw .context-menu) { background: var(--island-bg-color) !important; - border: 2px solid var(--color-gray-85) !important; - border-radius: 2px !important; - box-shadow: 4px 4px 0 var(--color-gray-85) !important; - transform: rotate(-0.2deg) !important; + border: 1px solid var(--default-border-color) !important; + border-radius: var(--border-radius-lg) !important; + box-shadow: var(--shadow-island-stronger) !important; padding: 2px !important; } :global(.excalidraw .context-menu-item) { - border-radius: 2px !important; + border-radius: var(--border-radius-md) !important; color: var(--color-gray-85) !important; font-weight: 500 !important; padding: 6px 12px !important; @@ -27,11 +26,10 @@ :global(.excalidraw .context-menu-item:hover) { background: var(--color-primary-light) !important; color: var(--color-primary-darkest) !important; - transform: translateX(1px) !important; } :global(.excalidraw .context-menu-item-separator) { - border-top: 2px solid var(--color-gray-30) !important; + border-top: 1px solid var(--default-border-color) !important; margin: 2px 4px !important; } diff --git a/frontend/src/components/Layout/Layout.module.scss b/frontend/src/components/Layout/Layout.module.scss index 14db8b7..1a58679 100644 --- a/frontend/src/components/Layout/Layout.module.scss +++ b/frontend/src/components/Layout/Layout.module.scss @@ -8,7 +8,7 @@ .sidebar { width: var(--sidebar-width); background: var(--island-bg-color); - border-right: 2px solid var(--color-gray-85); + border-right: 1px solid var(--default-border-color); display: flex; flex-direction: column; padding: var(--space-4); @@ -18,15 +18,6 @@ bottom: 0; z-index: 100; transition: transform var(--duration-normal) var(--ease-out); - box-shadow: 3px 0 0 var(--color-gray-85); - background-image: - repeating-linear-gradient( - 0deg, - transparent, - transparent 23px, - var(--color-gray-20) 23px, - var(--color-gray-20) 24px - ); @media (max-width: 768px) { transform: translateX(-100%); @@ -81,17 +72,16 @@ } .logoImg { - width: 28px; + width: auto; height: 28px; flex-shrink: 0; - filter: drop-shadow(0 1px 1px rgba(0,0,0,0.1)); } .logoMark { width: 32px; height: 32px; - border: 2px solid var(--color-gray-85); - border-radius: 9px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-md); color: var(--color-gray-85); background: var(--color-primary-light); display: inline-flex; @@ -99,7 +89,6 @@ justify-content: center; font-size: var(--text-lg); font-weight: 800; - transform: rotate(-4deg); flex-shrink: 0; } @@ -141,25 +130,22 @@ padding: var(--space-3) var(--space-4); color: var(--color-gray-70); text-decoration: none; - border: 2px solid transparent; - border-radius: 2px; + border: 1px solid transparent; + border-radius: var(--border-radius-lg); transition: all var(--duration-fast) var(--ease-out); font-weight: 500; &:hover { background: var(--color-surface-low); color: var(--color-on-surface); - border-color: var(--color-gray-30); - transform: rotate(-0.5deg); + border-color: var(--default-border-color); } &.active { background: var(--color-surface-primary-container); color: var(--color-primary-darkest); font-weight: 600; - border-color: var(--color-gray-85); - box-shadow: 2px 2px 0 var(--color-gray-85); - transform: rotate(-0.3deg); + border-color: var(--color-primary); } } @@ -236,8 +222,7 @@ .header { height: var(--header-height); background: var(--island-bg-color); - border-bottom: 2px solid var(--color-gray-85); - box-shadow: 0 3px 0 var(--color-gray-85); + border-bottom: 1px solid var(--default-border-color); display: flex; align-items: center; justify-content: space-between; @@ -297,11 +282,11 @@ .iconButton { position: relative; background: none; - border: 2px solid transparent; + border: 1px solid transparent; color: var(--color-gray-60); cursor: pointer; padding: var(--space-2); - border-radius: 2px; + border-radius: var(--border-radius-lg); display: inline-flex; align-items: center; justify-content: center; @@ -310,9 +295,7 @@ &:hover { color: var(--color-on-surface); background: var(--color-surface-low); - border-color: var(--color-gray-30); - box-shadow: 2px 2px 0 var(--color-gray-85); - transform: rotate(-1deg); + border-color: var(--default-border-color); } } @@ -396,8 +379,8 @@ .nameModal { background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); box-shadow: var(--modal-shadow); padding: var(--space-5); width: 360px; @@ -483,14 +466,13 @@ top: calc(100% + var(--space-2)); right: 100px; background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 5px 5px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island-stronger); width: 320px; max-height: 400px; overflow-y: auto; z-index: 100; - transform: rotate(-0.2deg); } .notifHeader { @@ -498,14 +480,14 @@ align-items: center; justify-content: space-between; padding: var(--space-3) var(--space-4); - border-bottom: 2px solid var(--color-gray-85); + border-bottom: 1px solid var(--default-border-color); } .notifTitle { font-weight: 600; font-size: var(--text-sm); color: var(--color-gray-85); - font-family: 'Georgia', serif; + font-family: var(--ui-font); } .notifMarkAll { diff --git a/frontend/src/components/Layout/Sidebar.tsx b/frontend/src/components/Layout/Sidebar.tsx index f2e9f5e..b58a261 100644 --- a/frontend/src/components/Layout/Sidebar.tsx +++ b/frontend/src/components/Layout/Sidebar.tsx @@ -41,10 +41,9 @@ export const Sidebar: React.FC = ({ open, onClose }) => { src="https://plus.excalidraw.com/images/logo.svg" alt="Excalidraw" className={styles.logoImg} - width={28} + width={120} height={28} /> - Excalidraw {onClose && ( - -
- - setNewDrawingName(e.target.value)} - onKeyDown={(e) => { if (e.key === 'Enter') confirmCreateDrawing(); if (e.key === 'Escape') setShowNameModal(false); }} - className={styles.modalInput} - /> -
-
- - -
- - - )} ); }; diff --git a/frontend/src/pages/Editor/Editor.module.scss b/frontend/src/pages/Editor/Editor.module.scss index 38f44ff..241cce9 100644 --- a/frontend/src/pages/Editor/Editor.module.scss +++ b/frontend/src/pages/Editor/Editor.module.scss @@ -407,18 +407,17 @@ align-items: center; gap: var(--space-3); background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); padding: var(--space-2) var(--space-4); - box-shadow: 3px 3px 0 var(--color-gray-85); - transform: rotate(-0.3deg); + box-shadow: var(--shadow-island); } .presentationLabel { font-size: var(--text-sm); color: var(--color-gray-70); font-weight: 500; - font-family: 'Georgia', serif; + font-family: var(--ui-font); } .modalOverlay { @@ -433,8 +432,8 @@ .modal { background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); box-shadow: var(--modal-shadow); width: 420px; max-width: 90vw; diff --git a/frontend/src/pages/Editor/Editor.tsx b/frontend/src/pages/Editor/Editor.tsx index dbb5e5e..2887d41 100644 --- a/frontend/src/pages/Editor/Editor.tsx +++ b/frontend/src/pages/Editor/Editor.tsx @@ -34,8 +34,15 @@ interface EditorState { function prepareElementsForImport(sourceElements: LooseElement[], offsetX: number, offsetY: number): LooseElement[] { if (!sourceElements || !sourceElements.length) return []; const idMap = new Map(); + const groupIdMap = new Map(); sourceElements.forEach((el) => { idMap.set(el.id as string, `${el.type}-${Math.random().toString(36).slice(2, 9)}`); + const gids = ((el as { groupIds?: string[] }).groupIds) || []; + gids.forEach((gid) => { + if (!groupIdMap.has(gid)) { + groupIdMap.set(gid, `group-${Math.random().toString(36).slice(2, 9)}`); + } + }); }); return sourceElements.map((el) => { const newEl: LooseElement = { ...el }; @@ -55,6 +62,10 @@ function prepareElementsForImport(sourceElements: LooseElement[], offsetX: numbe if (newEl.containerId && idMap.has(newEl.containerId as string)) { newEl.containerId = idMap.get(newEl.containerId as string); } + const gids = (newEl as { groupIds?: string[] }).groupIds; + if (gids && gids.length) { + (newEl as { groupIds?: string[] }).groupIds = gids.map((gid) => groupIdMap.get(gid) || gid); + } return newEl; }); } diff --git a/frontend/src/pages/FileBrowser/FileBrowser.module.scss b/frontend/src/pages/FileBrowser/FileBrowser.module.scss index 2ce4b7a..354b0bb 100644 --- a/frontend/src/pages/FileBrowser/FileBrowser.module.scss +++ b/frontend/src/pages/FileBrowser/FileBrowser.module.scss @@ -18,10 +18,9 @@ flex-wrap: wrap; padding: var(--space-5); background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 4px 4px 0 var(--color-gray-85); - transform: rotate(0.2deg); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island-stronger); @media (max-width: 640px) { flex-direction: column; @@ -115,9 +114,9 @@ width: 240px; flex-shrink: 0; background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 3px 3px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island); padding: var(--space-3); align-self: flex-start; @@ -140,12 +139,12 @@ align-items: center; gap: var(--space-3); padding: var(--space-2) var(--space-3); - border-radius: 2px; + border-radius: var(--border-radius-lg); color: var(--color-gray-70); cursor: pointer; transition: all var(--duration-fast) var(--ease-out); background: none; - border: 2px solid transparent; + border: 1px solid transparent; width: 100%; text-align: left; font-size: var(--text-sm); @@ -153,17 +152,14 @@ &:hover { background: var(--color-surface-low); color: var(--color-on-surface); - border-color: var(--color-gray-30); - transform: rotate(-0.3deg); + border-color: var(--default-border-color); } &.folderActive { background: var(--color-surface-primary-container); color: var(--color-primary-darkest); font-weight: 600; - border-color: var(--color-gray-85); - box-shadow: 2px 2px 0 var(--color-gray-85); - transform: rotate(-0.2deg); + border-color: var(--color-primary); } svg { @@ -228,15 +224,13 @@ .drawingCard { position: relative; - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 3px 3px 0 var(--color-gray-85); - transform: rotate(0.1deg); - transition: transform 0.15s ease, box-shadow 0.15s ease; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island); + transition: box-shadow 0.15s ease; &:hover { - transform: rotate(0) translate(-1px, -1px); - box-shadow: 5px 5px 0 var(--color-gray-85); + box-shadow: var(--shadow-island-stronger); } } @@ -312,9 +306,9 @@ top: calc(100% + var(--space-1)); right: 0; background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 3px 3px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island); min-width: 160px; z-index: 10; display: flex; @@ -328,7 +322,7 @@ text-align: left; padding: var(--space-2) var(--space-3); cursor: pointer; - border-radius: var(--border-radius-sm); + border-radius: var(--border-radius-md); color: var(--color-on-surface); font-size: var(--text-sm); @@ -371,58 +365,55 @@ flex-wrap: wrap; padding: var(--space-3); background: var(--color-surface-low); - border: 2px solid var(--color-gray-30); - border-radius: 2px; - box-shadow: 2px 2px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island); } .newProjectInput { flex: 1; min-width: 120px; background: var(--input-bg-color); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); padding: var(--space-2) var(--space-3); color: var(--text-primary-color); - font-size: var(--text-sm); &:focus { outline: none; border-color: var(--color-primary); - box-shadow: 3px 3px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); } } .newProjectBtn { background: var(--color-primary); color: #fff; - border: 2px solid var(--color-gray-85); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); padding: var(--space-2) var(--space-3); cursor: pointer; font-size: var(--text-sm); font-weight: 500; - box-shadow: 2px 2px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); &:hover { background: var(--color-primary-darkest); - transform: rotate(-0.5deg); } } .newProjectBtnCancel { background: none; - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); padding: var(--space-2) var(--space-3); cursor: pointer; font-size: var(--text-sm); color: var(--color-on-surface); - box-shadow: 2px 2px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); &:hover { background: var(--color-surface-low); - transform: rotate(-0.5deg); } } @@ -466,12 +457,11 @@ .modal { background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 5px 5px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island-stronger); width: 420px; max-width: 90vw; - transform: rotate(-0.3deg); } .modalHeader { @@ -479,13 +469,13 @@ align-items: center; justify-content: space-between; padding: var(--space-4) var(--space-5); - border-bottom: 2px solid var(--color-gray-85); + border-bottom: 1px solid var(--default-border-color); h3 { margin: 0; font-size: var(--text-lg); color: var(--color-gray-85); - font-family: 'Georgia', serif; + font-family: var(--ui-font); } } @@ -514,8 +504,8 @@ .modalInput { width: 100%; padding: var(--space-2) var(--space-3); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); background: var(--input-bg-color); color: var(--color-on-surface); font-size: var(--text-sm); @@ -523,7 +513,7 @@ &:focus { border-color: var(--color-primary); - box-shadow: 3px 3px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); } } @@ -536,27 +526,27 @@ .modalBtnSecondary { padding: var(--space-2) var(--space-4); - border-radius: 2px; - border: 2px solid var(--color-gray-30); + border-radius: var(--border-radius-lg); + border: 1px solid var(--default-border-color); background: transparent; color: var(--color-gray-70); font-size: var(--text-sm); cursor: pointer; - box-shadow: 2px 2px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); - &:hover { background: var(--color-surface-low); transform: rotate(-0.5deg); } + &:hover { background: var(--color-surface-low); } } .modalBtnPrimary { padding: var(--space-2) var(--space-4); - border-radius: 2px; - border: 2px solid var(--color-gray-85); + border-radius: var(--border-radius-lg); + border: 1px solid var(--default-border-color); background: var(--color-primary); color: white; font-size: var(--text-sm); cursor: pointer; - box-shadow: 2px 2px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); - &:hover { background: var(--color-primary-darker); transform: rotate(-0.5deg); } + &:hover { background: var(--color-primary-darker); } &:disabled { opacity: 0.6; cursor: not-allowed; } } diff --git a/frontend/src/pages/Settings/Settings.module.scss b/frontend/src/pages/Settings/Settings.module.scss index 06c9bf3..dcf7e29 100644 --- a/frontend/src/pages/Settings/Settings.module.scss +++ b/frontend/src/pages/Settings/Settings.module.scss @@ -9,17 +9,16 @@ margin-bottom: var(--space-8); padding: var(--space-5); background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 4px 4px 0 var(--color-gray-85); - transform: rotate(0.1deg); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island-stronger); h1 { font-size: var(--text-3xl); font-weight: 700; color: var(--color-gray-85); margin-bottom: var(--space-2); - font-family: 'Georgia', serif; + font-family: var(--ui-font); } } @@ -45,10 +44,10 @@ align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-4); - border-radius: 2px; + border-radius: var(--border-radius-lg); color: var(--color-gray-70); background: none; - border: 2px solid transparent; + border: 1px solid transparent; cursor: pointer; font-size: var(--text-sm); transition: all var(--duration-fast) var(--ease-out); @@ -57,17 +56,14 @@ &:hover { background: var(--color-surface-low); color: var(--color-on-surface); - border-color: var(--color-gray-30); - transform: rotate(-0.2deg); + border-color: var(--default-border-color); } &.active { background: var(--color-surface-primary-container); color: var(--color-primary-darkest); font-weight: 600; - border-color: var(--color-gray-85); - box-shadow: 2px 2px 0 var(--color-gray-85); - transform: rotate(-0.1deg); + border-color: var(--color-primary); } } @@ -96,8 +92,8 @@ font-size: var(--text-2xl); font-weight: 700; overflow: hidden; - border: 2px solid var(--color-gray-85); - box-shadow: 3px 3px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + box-shadow: var(--shadow-island); img { width: 100%; @@ -151,26 +147,24 @@ .themeOption { padding: var(--space-2) var(--space-4); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); background: var(--island-bg-color); color: var(--color-gray-70); font-size: var(--text-sm); cursor: pointer; transition: all var(--duration-fast) var(--ease-out); - box-shadow: 2px 2px 0 var(--color-gray-30); + box-shadow: var(--shadow-island); &:hover { border-color: var(--color-primary); color: var(--color-primary); - transform: translate(-1px, -1px); - box-shadow: 3px 3px 0 var(--color-primary); + box-shadow: var(--shadow-island-stronger); } &.active { background: var(--color-primary); - border-color: var(--color-gray-85); + border-color: var(--color-primary); color: white; - box-shadow: 2px 2px 0 var(--color-gray-85); } } diff --git a/frontend/src/pages/Team/Team.module.scss b/frontend/src/pages/Team/Team.module.scss index 40941f8..35b6b04 100644 --- a/frontend/src/pages/Team/Team.module.scss +++ b/frontend/src/pages/Team/Team.module.scss @@ -9,17 +9,16 @@ margin-bottom: var(--space-8); padding: var(--space-5); background: var(--island-bg-color); - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 4px 4px 0 var(--color-gray-85); - transform: rotate(-0.2deg); + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island-stronger); h1 { font-size: var(--text-3xl); font-weight: 700; color: var(--color-gray-85); margin-bottom: var(--space-2); - font-family: 'Georgia', serif; + font-family: var(--ui-font); } } @@ -68,21 +67,20 @@ gap: var(--space-3); padding: var(--space-3) var(--space-2); margin-bottom: var(--space-2); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); transition: all 0.15s ease; - box-shadow: 2px 2px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); background: var(--island-bg-color); &:hover { border-color: var(--color-primary); background: var(--color-surface-low); - transform: translateX(2px) rotate(-0.2deg); - box-shadow: 3px 3px 0 var(--color-gray-85); + box-shadow: var(--shadow-island-stronger); } &:last-child { - border-bottom: 2px solid var(--color-gray-30); + border-bottom: 1px solid var(--default-border-color); margin-bottom: 0; } } @@ -98,8 +96,8 @@ justify-content: center; font-weight: 700; flex-shrink: 0; - border: 2px solid var(--color-gray-85); - box-shadow: 2px 2px 0 var(--color-gray-85); + border: 1px solid var(--default-border-color); + box-shadow: var(--shadow-island); } .memberInfo { @@ -122,13 +120,12 @@ gap: var(--space-1); padding: var(--space-1) var(--space-3); background: var(--color-surface-low); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); font-size: var(--text-xs); font-weight: 500; color: var(--color-gray-70); text-transform: capitalize; - box-shadow: 1px 1px 0 var(--color-gray-85); } .inviteForm { @@ -139,15 +136,15 @@ .inviteInput { padding: var(--space-3); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); font-size: var(--text-sm); background: var(--input-bg-color); &:focus { outline: none; border-color: var(--color-primary); - box-shadow: 3px 3px 0 var(--color-gray-85); + box-shadow: var(--shadow-island); } } @@ -187,12 +184,11 @@ .roleSelect { padding: var(--space-2) var(--space-3); - border: 2px solid var(--color-gray-30); - border-radius: 2px; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); font-size: var(--text-sm); background: var(--input-bg-color); cursor: pointer; - box-shadow: 1px 1px 0 var(--color-gray-85); } .error { diff --git a/frontend/src/pages/Templates/Templates.module.scss b/frontend/src/pages/Templates/Templates.module.scss index b9eb4bd..87bbce7 100644 --- a/frontend/src/pages/Templates/Templates.module.scss +++ b/frontend/src/pages/Templates/Templates.module.scss @@ -78,15 +78,13 @@ .templateCard { overflow: hidden; - border: 2px solid var(--color-gray-85); - border-radius: 2px; - box-shadow: 3px 3px 0 var(--color-gray-85); - transform: rotate(0.1deg); - transition: transform 0.15s ease, box-shadow 0.15s ease; + border: 1px solid var(--default-border-color); + border-radius: var(--border-radius-lg); + box-shadow: var(--shadow-island); + transition: box-shadow 0.15s ease; &:hover { - transform: rotate(0) translate(-1px, -1px); - box-shadow: 5px 5px 0 var(--color-gray-85); + box-shadow: var(--shadow-island-stronger); } } diff --git a/frontend/src/styles/global.scss b/frontend/src/styles/global.scss index cb75894..fc83fdd 100644 --- a/frontend/src/styles/global.scss +++ b/frontend/src/styles/global.scss @@ -122,17 +122,17 @@ a { // ============================================ .excalidraw { - --border-radius-md: 2px; + --border-radius-md: var(--border-radius-lg); .context-menu { - border: 2px solid var(--color-gray-85) !important; - border-radius: 2px !important; - box-shadow: 3px 3px 0 var(--color-gray-85) !important; + border: 1px solid var(--default-border-color) !important; + border-radius: var(--border-radius-lg) !important; + box-shadow: var(--shadow-island) !important; } .library-menu-items-container { - border: 2px solid var(--color-gray-85) !important; - border-radius: 2px !important; - box-shadow: 3px 3px 0 var(--color-gray-85) !important; + border: 1px solid var(--default-border-color) !important; + border-radius: var(--border-radius-lg) !important; + box-shadow: var(--shadow-island) !important; } } diff --git a/frontend/test-results/.last-run.json b/frontend/test-results/.last-run.json index dc87bc4..5fca3f8 100644 --- a/frontend/test-results/.last-run.json +++ b/frontend/test-results/.last-run.json @@ -1,11 +1,4 @@ { "status": "failed", - "failedTests": [ - "c31ff144dc4fee3acd0a-bec551c658216ec9862a", - "c31ff144dc4fee3acd0a-f87315abf5d197970540", - "c31ff144dc4fee3acd0a-fc5e81ebcffdb7687b8e", - "c31ff144dc4fee3acd0a-989f5dcca4211fe0b2e4", - "c31ff144dc4fee3acd0a-ac5aa3cfe7537125a151", - "c31ff144dc4fee3acd0a-7f990aaafdc09c3794e8" - ] + "failedTests": [] } \ No newline at end of file diff --git a/frontend/test-results/app-dashboard-shows-stats-cards-chromium/error-context.md b/frontend/test-results/app-dashboard-shows-stats-cards-chromium/error-context.md deleted file mode 100644 index 8faa68b..0000000 --- a/frontend/test-results/app-dashboard-shows-stats-cards-chromium/error-context.md +++ /dev/null @@ -1,162 +0,0 @@ -# Test info - -- Name: dashboard >> shows stats cards -- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:45:3 - -# Error details - -``` -Error: Error reading storage state from playwright/.auth/state.json: -ENOENT: no such file or directory, open 'playwright/.auth/state.json' -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | - 3 | const BASE = 'http://localhost:3456'; - 4 | - 5 | // Auth: first-run signup, blocked signup, login - 6 | test.describe.serial('auth flow', () => { - 7 | test.use({ storageState: { cookies: [], origins: [] } }); - 8 | - 9 | test('redirects to signup when no users exist', async ({ page }) => { - 10 | await page.goto(BASE + '/'); - 11 | await expect(page).toHaveURL(/\/signup$/); - 12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible(); - 13 | }); - 14 | - 15 | test('first user can signup', async ({ page }) => { - 16 | await page.goto(BASE + '/signup'); - 17 | await page.getByLabel('Full Name').fill('E2E User'); - 18 | await page.getByLabel('Email').fill('e2e@test.com'); - 19 | await page.getByLabel('Password').fill('e2e-password-123'); - 20 | await page.getByRole('button', { name: 'Create Account' }).click(); - 21 | await expect(page).toHaveURL(BASE + '/'); - 22 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 23 | await page.context().storageState({ path: 'playwright/.auth/state.json' }); - 24 | }); - 25 | - 26 | test('blocks second signup when users exist', async ({ page }) => { - 27 | await page.goto(BASE + '/signup'); - 28 | await expect(page).toHaveURL(/\/login$/); - 29 | }); - 30 | - 31 | test('existing user can login', async ({ page }) => { - 32 | await page.goto(BASE + '/login'); - 33 | await page.getByLabel('Email').fill('e2e@test.com'); - 34 | await page.getByLabel('Password').fill('e2e-password-123'); - 35 | await page.getByRole('button', { name: 'Sign In' }).click(); - 36 | await expect(page).toHaveURL(BASE + '/'); - 37 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 38 | }); - 39 | }); - 40 | - 41 | // Dashboard: quick actions and stats - 42 | test.describe.serial('dashboard', () => { - 43 | test.use({ storageState: 'playwright/.auth/state.json' }); - 44 | -> 45 | test('shows stats cards', async ({ page }) => { - | ^ Error: Error reading storage state from playwright/.auth/state.json: - 46 | await page.goto(BASE + '/'); - 47 | await expect(page.getByText('Drawings')).toBeVisible(); - 48 | await expect(page.getByText('Projects')).toBeVisible(); - 49 | await expect(page.getByText('Teams')).toBeVisible(); - 50 | }); - 51 | - 52 | test('quick action: New Project navigates to files', async ({ page }) => { - 53 | await page.goto(BASE + '/'); - 54 | await page.getByRole('button', { name: 'New Project' }).click(); - 55 | await expect(page).toHaveURL(/\/files/); - 56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible(); - 57 | await expect(page.getByText('All Projects')).toBeVisible(); - 58 | }); - 59 | - 60 | test('quick action: Invite navigates to team', async ({ page }) => { - 61 | await page.goto(BASE + '/'); - 62 | await page.getByRole('button', { name: 'Invite' }).click(); - 63 | await expect(page).toHaveURL(/\/team/); - 64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 65 | }); - 66 | - 67 | test('quick action: Library navigates to marketplace', async ({ page }) => { - 68 | await page.goto(BASE + '/'); - 69 | await page.getByRole('button', { name: 'Library' }).click(); - 70 | await expect(page).toHaveURL(/\/library/); - 71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 72 | }); - 73 | - 74 | test('New Drawing opens template picker', async ({ page }) => { - 75 | await page.goto(BASE + '/'); - 76 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 77 | await expect(page.getByRole('dialog')).toBeVisible(); - 78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible(); - 79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible(); - 80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible(); - 81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible(); - 82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible(); - 83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible(); - 84 | }); - 85 | }); - 86 | - 87 | // Projects / FileBrowser - 88 | test.describe.serial('projects', () => { - 89 | test.use({ storageState: 'playwright/.auth/state.json' }); - 90 | - 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => { - 92 | await page.goto(BASE + '/files'); - 93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible(); - 94 | await expect(page.getByText('All Projects')).toBeVisible(); - 95 | }); - 96 | - 97 | test('can create a drawing from file browser', async ({ page }) => { - 98 | await page.goto(BASE + '/files'); - 99 | await page.getByRole('button', { name: 'Create new drawing' }).click(); - 100 | await expect(page.getByRole('dialog')).toBeVisible(); - 101 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 102 | await expect(page).toHaveURL(/\/drawing\//); - 103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible(); - 104 | }); - 105 | }); - 106 | - 107 | // Editor / Canvas - 108 | test.describe.serial('editor', () => { - 109 | test.use({ storageState: 'playwright/.auth/state.json' }); - 110 | - 111 | test('creates drawing with To-Do template', async ({ page }) => { - 112 | await page.goto(BASE + '/'); - 113 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 114 | await page.getByRole('button', { name: 'To-Do List' }).click(); - 115 | await expect(page).toHaveURL(/\/drawing\//); - 116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 117 | }); - 118 | - 119 | test('editor shows save controls and back button', async ({ page }) => { - 120 | await page.goto(BASE + '/'); - 121 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 122 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 123 | await expect(page).toHaveURL(/\/drawing\//); - 124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible(); - 126 | }); - 127 | }); - 128 | - 129 | // Library Marketplace - 130 | test.describe.serial('library', () => { - 131 | test.use({ storageState: 'playwright/.auth/state.json' }); - 132 | - 133 | test('loads marketplace with search and categories', async ({ page }) => { - 134 | await page.goto(BASE + '/library'); - 135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible(); - 137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible(); - 138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible(); - 139 | }); - 140 | - 141 | test('search filters libraries', async ({ page }) => { - 142 | await page.goto(BASE + '/library'); - 143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent'); - 144 | await expect(page.getByText('No libraries found')).toBeVisible(); - 145 | }); -``` \ No newline at end of file diff --git a/frontend/test-results/app-editor-creates-drawing-with-To-Do-template-chromium/error-context.md b/frontend/test-results/app-editor-creates-drawing-with-To-Do-template-chromium/error-context.md deleted file mode 100644 index 54b2cf5..0000000 --- a/frontend/test-results/app-editor-creates-drawing-with-To-Do-template-chromium/error-context.md +++ /dev/null @@ -1,177 +0,0 @@ -# Test info - -- Name: editor >> creates drawing with To-Do template -- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:111:3 - -# Error details - -``` -Error: Error reading storage state from playwright/.auth/state.json: -ENOENT: no such file or directory, open 'playwright/.auth/state.json' -``` - -# Test source - -```ts - 11 | await expect(page).toHaveURL(/\/signup$/); - 12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible(); - 13 | }); - 14 | - 15 | test('first user can signup', async ({ page }) => { - 16 | await page.goto(BASE + '/signup'); - 17 | await page.getByLabel('Full Name').fill('E2E User'); - 18 | await page.getByLabel('Email').fill('e2e@test.com'); - 19 | await page.getByLabel('Password').fill('e2e-password-123'); - 20 | await page.getByRole('button', { name: 'Create Account' }).click(); - 21 | await expect(page).toHaveURL(BASE + '/'); - 22 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 23 | await page.context().storageState({ path: 'playwright/.auth/state.json' }); - 24 | }); - 25 | - 26 | test('blocks second signup when users exist', async ({ page }) => { - 27 | await page.goto(BASE + '/signup'); - 28 | await expect(page).toHaveURL(/\/login$/); - 29 | }); - 30 | - 31 | test('existing user can login', async ({ page }) => { - 32 | await page.goto(BASE + '/login'); - 33 | await page.getByLabel('Email').fill('e2e@test.com'); - 34 | await page.getByLabel('Password').fill('e2e-password-123'); - 35 | await page.getByRole('button', { name: 'Sign In' }).click(); - 36 | await expect(page).toHaveURL(BASE + '/'); - 37 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 38 | }); - 39 | }); - 40 | - 41 | // Dashboard: quick actions and stats - 42 | test.describe.serial('dashboard', () => { - 43 | test.use({ storageState: 'playwright/.auth/state.json' }); - 44 | - 45 | test('shows stats cards', async ({ page }) => { - 46 | await page.goto(BASE + '/'); - 47 | await expect(page.getByText('Drawings')).toBeVisible(); - 48 | await expect(page.getByText('Projects')).toBeVisible(); - 49 | await expect(page.getByText('Teams')).toBeVisible(); - 50 | }); - 51 | - 52 | test('quick action: New Project navigates to files', async ({ page }) => { - 53 | await page.goto(BASE + '/'); - 54 | await page.getByRole('button', { name: 'New Project' }).click(); - 55 | await expect(page).toHaveURL(/\/files/); - 56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible(); - 57 | await expect(page.getByText('All Projects')).toBeVisible(); - 58 | }); - 59 | - 60 | test('quick action: Invite navigates to team', async ({ page }) => { - 61 | await page.goto(BASE + '/'); - 62 | await page.getByRole('button', { name: 'Invite' }).click(); - 63 | await expect(page).toHaveURL(/\/team/); - 64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 65 | }); - 66 | - 67 | test('quick action: Library navigates to marketplace', async ({ page }) => { - 68 | await page.goto(BASE + '/'); - 69 | await page.getByRole('button', { name: 'Library' }).click(); - 70 | await expect(page).toHaveURL(/\/library/); - 71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 72 | }); - 73 | - 74 | test('New Drawing opens template picker', async ({ page }) => { - 75 | await page.goto(BASE + '/'); - 76 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 77 | await expect(page.getByRole('dialog')).toBeVisible(); - 78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible(); - 79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible(); - 80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible(); - 81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible(); - 82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible(); - 83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible(); - 84 | }); - 85 | }); - 86 | - 87 | // Projects / FileBrowser - 88 | test.describe.serial('projects', () => { - 89 | test.use({ storageState: 'playwright/.auth/state.json' }); - 90 | - 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => { - 92 | await page.goto(BASE + '/files'); - 93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible(); - 94 | await expect(page.getByText('All Projects')).toBeVisible(); - 95 | }); - 96 | - 97 | test('can create a drawing from file browser', async ({ page }) => { - 98 | await page.goto(BASE + '/files'); - 99 | await page.getByRole('button', { name: 'Create new drawing' }).click(); - 100 | await expect(page.getByRole('dialog')).toBeVisible(); - 101 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 102 | await expect(page).toHaveURL(/\/drawing\//); - 103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible(); - 104 | }); - 105 | }); - 106 | - 107 | // Editor / Canvas - 108 | test.describe.serial('editor', () => { - 109 | test.use({ storageState: 'playwright/.auth/state.json' }); - 110 | -> 111 | test('creates drawing with To-Do template', async ({ page }) => { - | ^ Error: Error reading storage state from playwright/.auth/state.json: - 112 | await page.goto(BASE + '/'); - 113 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 114 | await page.getByRole('button', { name: 'To-Do List' }).click(); - 115 | await expect(page).toHaveURL(/\/drawing\//); - 116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 117 | }); - 118 | - 119 | test('editor shows save controls and back button', async ({ page }) => { - 120 | await page.goto(BASE + '/'); - 121 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 122 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 123 | await expect(page).toHaveURL(/\/drawing\//); - 124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible(); - 126 | }); - 127 | }); - 128 | - 129 | // Library Marketplace - 130 | test.describe.serial('library', () => { - 131 | test.use({ storageState: 'playwright/.auth/state.json' }); - 132 | - 133 | test('loads marketplace with search and categories', async ({ page }) => { - 134 | await page.goto(BASE + '/library'); - 135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible(); - 137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible(); - 138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible(); - 139 | }); - 140 | - 141 | test('search filters libraries', async ({ page }) => { - 142 | await page.goto(BASE + '/library'); - 143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent'); - 144 | await expect(page.getByText('No libraries found')).toBeVisible(); - 145 | }); - 146 | }); - 147 | - 148 | // Team / Invites - 149 | test.describe.serial('team', () => { - 150 | test.use({ storageState: 'playwright/.auth/state.json' }); - 151 | - 152 | test('shows owner in members list', async ({ page }) => { - 153 | await page.goto(BASE + '/team'); - 154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 155 | await expect(page.getByText('E2E User')).toBeVisible(); - 156 | await expect(page.getByText('owner')).toBeVisible(); - 157 | }); - 158 | - 159 | test('can send team invite', async ({ page }) => { - 160 | await page.goto(BASE + '/team'); - 161 | await page.getByLabel('Email address').fill('invited@test.com'); - 162 | await page.locator('select').selectOption('editor'); - 163 | await page.getByRole('button', { name: 'Send Invite' }).click(); - 164 | await expect(page.getByText('Invite sent!')).toBeVisible(); - 165 | await expect(page.getByText('Pending Invites')).toBeVisible(); - 166 | await expect(page.getByText('invited@test.com')).toBeVisible(); - 167 | await expect(page.getByText('editor').first()).toBeVisible(); - 168 | }); - 169 | }); - 170 | -``` \ No newline at end of file diff --git a/frontend/test-results/app-library-loads-marketplace-with-search-and-categories-chromium/error-context.md b/frontend/test-results/app-library-loads-marketplace-with-search-and-categories-chromium/error-context.md deleted file mode 100644 index 77f61d1..0000000 --- a/frontend/test-results/app-library-loads-marketplace-with-search-and-categories-chromium/error-context.md +++ /dev/null @@ -1,155 +0,0 @@ -# Test info - -- Name: library >> loads marketplace with search and categories -- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:133:3 - -# Error details - -``` -Error: Error reading storage state from playwright/.auth/state.json: -ENOENT: no such file or directory, open 'playwright/.auth/state.json' -``` - -# Test source - -```ts - 33 | await page.getByLabel('Email').fill('e2e@test.com'); - 34 | await page.getByLabel('Password').fill('e2e-password-123'); - 35 | await page.getByRole('button', { name: 'Sign In' }).click(); - 36 | await expect(page).toHaveURL(BASE + '/'); - 37 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 38 | }); - 39 | }); - 40 | - 41 | // Dashboard: quick actions and stats - 42 | test.describe.serial('dashboard', () => { - 43 | test.use({ storageState: 'playwright/.auth/state.json' }); - 44 | - 45 | test('shows stats cards', async ({ page }) => { - 46 | await page.goto(BASE + '/'); - 47 | await expect(page.getByText('Drawings')).toBeVisible(); - 48 | await expect(page.getByText('Projects')).toBeVisible(); - 49 | await expect(page.getByText('Teams')).toBeVisible(); - 50 | }); - 51 | - 52 | test('quick action: New Project navigates to files', async ({ page }) => { - 53 | await page.goto(BASE + '/'); - 54 | await page.getByRole('button', { name: 'New Project' }).click(); - 55 | await expect(page).toHaveURL(/\/files/); - 56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible(); - 57 | await expect(page.getByText('All Projects')).toBeVisible(); - 58 | }); - 59 | - 60 | test('quick action: Invite navigates to team', async ({ page }) => { - 61 | await page.goto(BASE + '/'); - 62 | await page.getByRole('button', { name: 'Invite' }).click(); - 63 | await expect(page).toHaveURL(/\/team/); - 64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 65 | }); - 66 | - 67 | test('quick action: Library navigates to marketplace', async ({ page }) => { - 68 | await page.goto(BASE + '/'); - 69 | await page.getByRole('button', { name: 'Library' }).click(); - 70 | await expect(page).toHaveURL(/\/library/); - 71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 72 | }); - 73 | - 74 | test('New Drawing opens template picker', async ({ page }) => { - 75 | await page.goto(BASE + '/'); - 76 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 77 | await expect(page.getByRole('dialog')).toBeVisible(); - 78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible(); - 79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible(); - 80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible(); - 81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible(); - 82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible(); - 83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible(); - 84 | }); - 85 | }); - 86 | - 87 | // Projects / FileBrowser - 88 | test.describe.serial('projects', () => { - 89 | test.use({ storageState: 'playwright/.auth/state.json' }); - 90 | - 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => { - 92 | await page.goto(BASE + '/files'); - 93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible(); - 94 | await expect(page.getByText('All Projects')).toBeVisible(); - 95 | }); - 96 | - 97 | test('can create a drawing from file browser', async ({ page }) => { - 98 | await page.goto(BASE + '/files'); - 99 | await page.getByRole('button', { name: 'Create new drawing' }).click(); - 100 | await expect(page.getByRole('dialog')).toBeVisible(); - 101 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 102 | await expect(page).toHaveURL(/\/drawing\//); - 103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible(); - 104 | }); - 105 | }); - 106 | - 107 | // Editor / Canvas - 108 | test.describe.serial('editor', () => { - 109 | test.use({ storageState: 'playwright/.auth/state.json' }); - 110 | - 111 | test('creates drawing with To-Do template', async ({ page }) => { - 112 | await page.goto(BASE + '/'); - 113 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 114 | await page.getByRole('button', { name: 'To-Do List' }).click(); - 115 | await expect(page).toHaveURL(/\/drawing\//); - 116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 117 | }); - 118 | - 119 | test('editor shows save controls and back button', async ({ page }) => { - 120 | await page.goto(BASE + '/'); - 121 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 122 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 123 | await expect(page).toHaveURL(/\/drawing\//); - 124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible(); - 126 | }); - 127 | }); - 128 | - 129 | // Library Marketplace - 130 | test.describe.serial('library', () => { - 131 | test.use({ storageState: 'playwright/.auth/state.json' }); - 132 | -> 133 | test('loads marketplace with search and categories', async ({ page }) => { - | ^ Error: Error reading storage state from playwright/.auth/state.json: - 134 | await page.goto(BASE + '/library'); - 135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible(); - 137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible(); - 138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible(); - 139 | }); - 140 | - 141 | test('search filters libraries', async ({ page }) => { - 142 | await page.goto(BASE + '/library'); - 143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent'); - 144 | await expect(page.getByText('No libraries found')).toBeVisible(); - 145 | }); - 146 | }); - 147 | - 148 | // Team / Invites - 149 | test.describe.serial('team', () => { - 150 | test.use({ storageState: 'playwright/.auth/state.json' }); - 151 | - 152 | test('shows owner in members list', async ({ page }) => { - 153 | await page.goto(BASE + '/team'); - 154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 155 | await expect(page.getByText('E2E User')).toBeVisible(); - 156 | await expect(page.getByText('owner')).toBeVisible(); - 157 | }); - 158 | - 159 | test('can send team invite', async ({ page }) => { - 160 | await page.goto(BASE + '/team'); - 161 | await page.getByLabel('Email address').fill('invited@test.com'); - 162 | await page.locator('select').selectOption('editor'); - 163 | await page.getByRole('button', { name: 'Send Invite' }).click(); - 164 | await expect(page.getByText('Invite sent!')).toBeVisible(); - 165 | await expect(page.getByText('Pending Invites')).toBeVisible(); - 166 | await expect(page.getByText('invited@test.com')).toBeVisible(); - 167 | await expect(page.getByText('editor').first()).toBeVisible(); - 168 | }); - 169 | }); - 170 | -``` \ No newline at end of file diff --git a/frontend/test-results/app-projects-shows-Projects-label-in-sidebar-and-breadcrumb-chromium/error-context.md b/frontend/test-results/app-projects-shows-Projects-label-in-sidebar-and-breadcrumb-chromium/error-context.md deleted file mode 100644 index e6e1202..0000000 --- a/frontend/test-results/app-projects-shows-Projects-label-in-sidebar-and-breadcrumb-chromium/error-context.md +++ /dev/null @@ -1,187 +0,0 @@ -# Test info - -- Name: projects >> shows Projects label in sidebar and breadcrumb -- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:91:3 - -# Error details - -``` -Error: Error reading storage state from playwright/.auth/state.json: -ENOENT: no such file or directory, open 'playwright/.auth/state.json' -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | - 3 | const BASE = 'http://localhost:3456'; - 4 | - 5 | // Auth: first-run signup, blocked signup, login - 6 | test.describe.serial('auth flow', () => { - 7 | test.use({ storageState: { cookies: [], origins: [] } }); - 8 | - 9 | test('redirects to signup when no users exist', async ({ page }) => { - 10 | await page.goto(BASE + '/'); - 11 | await expect(page).toHaveURL(/\/signup$/); - 12 | await expect(page.getByRole('heading', { name: 'Create account' })).toBeVisible(); - 13 | }); - 14 | - 15 | test('first user can signup', async ({ page }) => { - 16 | await page.goto(BASE + '/signup'); - 17 | await page.getByLabel('Full Name').fill('E2E User'); - 18 | await page.getByLabel('Email').fill('e2e@test.com'); - 19 | await page.getByLabel('Password').fill('e2e-password-123'); - 20 | await page.getByRole('button', { name: 'Create Account' }).click(); - 21 | await expect(page).toHaveURL(BASE + '/'); - 22 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 23 | await page.context().storageState({ path: 'playwright/.auth/state.json' }); - 24 | }); - 25 | - 26 | test('blocks second signup when users exist', async ({ page }) => { - 27 | await page.goto(BASE + '/signup'); - 28 | await expect(page).toHaveURL(/\/login$/); - 29 | }); - 30 | - 31 | test('existing user can login', async ({ page }) => { - 32 | await page.goto(BASE + '/login'); - 33 | await page.getByLabel('Email').fill('e2e@test.com'); - 34 | await page.getByLabel('Password').fill('e2e-password-123'); - 35 | await page.getByRole('button', { name: 'Sign In' }).click(); - 36 | await expect(page).toHaveURL(BASE + '/'); - 37 | await expect(page.getByText(/Welcome back/)).toBeVisible(); - 38 | }); - 39 | }); - 40 | - 41 | // Dashboard: quick actions and stats - 42 | test.describe.serial('dashboard', () => { - 43 | test.use({ storageState: 'playwright/.auth/state.json' }); - 44 | - 45 | test('shows stats cards', async ({ page }) => { - 46 | await page.goto(BASE + '/'); - 47 | await expect(page.getByText('Drawings')).toBeVisible(); - 48 | await expect(page.getByText('Projects')).toBeVisible(); - 49 | await expect(page.getByText('Teams')).toBeVisible(); - 50 | }); - 51 | - 52 | test('quick action: New Project navigates to files', async ({ page }) => { - 53 | await page.goto(BASE + '/'); - 54 | await page.getByRole('button', { name: 'New Project' }).click(); - 55 | await expect(page).toHaveURL(/\/files/); - 56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible(); - 57 | await expect(page.getByText('All Projects')).toBeVisible(); - 58 | }); - 59 | - 60 | test('quick action: Invite navigates to team', async ({ page }) => { - 61 | await page.goto(BASE + '/'); - 62 | await page.getByRole('button', { name: 'Invite' }).click(); - 63 | await expect(page).toHaveURL(/\/team/); - 64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 65 | }); - 66 | - 67 | test('quick action: Library navigates to marketplace', async ({ page }) => { - 68 | await page.goto(BASE + '/'); - 69 | await page.getByRole('button', { name: 'Library' }).click(); - 70 | await expect(page).toHaveURL(/\/library/); - 71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 72 | }); - 73 | - 74 | test('New Drawing opens template picker', async ({ page }) => { - 75 | await page.goto(BASE + '/'); - 76 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 77 | await expect(page.getByRole('dialog')).toBeVisible(); - 78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible(); - 79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible(); - 80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible(); - 81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible(); - 82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible(); - 83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible(); - 84 | }); - 85 | }); - 86 | - 87 | // Projects / FileBrowser - 88 | test.describe.serial('projects', () => { - 89 | test.use({ storageState: 'playwright/.auth/state.json' }); - 90 | -> 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => { - | ^ Error: Error reading storage state from playwright/.auth/state.json: - 92 | await page.goto(BASE + '/files'); - 93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible(); - 94 | await expect(page.getByText('All Projects')).toBeVisible(); - 95 | }); - 96 | - 97 | test('can create a drawing from file browser', async ({ page }) => { - 98 | await page.goto(BASE + '/files'); - 99 | await page.getByRole('button', { name: 'Create new drawing' }).click(); - 100 | await expect(page.getByRole('dialog')).toBeVisible(); - 101 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 102 | await expect(page).toHaveURL(/\/drawing\//); - 103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible(); - 104 | }); - 105 | }); - 106 | - 107 | // Editor / Canvas - 108 | test.describe.serial('editor', () => { - 109 | test.use({ storageState: 'playwright/.auth/state.json' }); - 110 | - 111 | test('creates drawing with To-Do template', async ({ page }) => { - 112 | await page.goto(BASE + '/'); - 113 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 114 | await page.getByRole('button', { name: 'To-Do List' }).click(); - 115 | await expect(page).toHaveURL(/\/drawing\//); - 116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 117 | }); - 118 | - 119 | test('editor shows save controls and back button', async ({ page }) => { - 120 | await page.goto(BASE + '/'); - 121 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 122 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 123 | await expect(page).toHaveURL(/\/drawing\//); - 124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible(); - 126 | }); - 127 | }); - 128 | - 129 | // Library Marketplace - 130 | test.describe.serial('library', () => { - 131 | test.use({ storageState: 'playwright/.auth/state.json' }); - 132 | - 133 | test('loads marketplace with search and categories', async ({ page }) => { - 134 | await page.goto(BASE + '/library'); - 135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible(); - 137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible(); - 138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible(); - 139 | }); - 140 | - 141 | test('search filters libraries', async ({ page }) => { - 142 | await page.goto(BASE + '/library'); - 143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent'); - 144 | await expect(page.getByText('No libraries found')).toBeVisible(); - 145 | }); - 146 | }); - 147 | - 148 | // Team / Invites - 149 | test.describe.serial('team', () => { - 150 | test.use({ storageState: 'playwright/.auth/state.json' }); - 151 | - 152 | test('shows owner in members list', async ({ page }) => { - 153 | await page.goto(BASE + '/team'); - 154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 155 | await expect(page.getByText('E2E User')).toBeVisible(); - 156 | await expect(page.getByText('owner')).toBeVisible(); - 157 | }); - 158 | - 159 | test('can send team invite', async ({ page }) => { - 160 | await page.goto(BASE + '/team'); - 161 | await page.getByLabel('Email address').fill('invited@test.com'); - 162 | await page.locator('select').selectOption('editor'); - 163 | await page.getByRole('button', { name: 'Send Invite' }).click(); - 164 | await expect(page.getByText('Invite sent!')).toBeVisible(); - 165 | await expect(page.getByText('Pending Invites')).toBeVisible(); - 166 | await expect(page.getByText('invited@test.com')).toBeVisible(); - 167 | await expect(page.getByText('editor').first()).toBeVisible(); - 168 | }); - 169 | }); - 170 | -``` \ No newline at end of file diff --git a/frontend/test-results/app-team-shows-owner-in-members-list-chromium/error-context.md b/frontend/test-results/app-team-shows-owner-in-members-list-chromium/error-context.md deleted file mode 100644 index 8037877..0000000 --- a/frontend/test-results/app-team-shows-owner-in-members-list-chromium/error-context.md +++ /dev/null @@ -1,136 +0,0 @@ -# Test info - -- Name: team >> shows owner in members list -- Location: /home/tdvorak/Desktop/PROG+HTML/Excalidraw/frontend/e2e/app.spec.ts:152:3 - -# Error details - -``` -Error: Error reading storage state from playwright/.auth/state.json: -ENOENT: no such file or directory, open 'playwright/.auth/state.json' -``` - -# Test source - -```ts - 52 | test('quick action: New Project navigates to files', async ({ page }) => { - 53 | await page.goto(BASE + '/'); - 54 | await page.getByRole('button', { name: 'New Project' }).click(); - 55 | await expect(page).toHaveURL(/\/files/); - 56 | await expect(page.getByRole('navigation', { name: 'Project tree' })).toBeVisible(); - 57 | await expect(page.getByText('All Projects')).toBeVisible(); - 58 | }); - 59 | - 60 | test('quick action: Invite navigates to team', async ({ page }) => { - 61 | await page.goto(BASE + '/'); - 62 | await page.getByRole('button', { name: 'Invite' }).click(); - 63 | await expect(page).toHaveURL(/\/team/); - 64 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 65 | }); - 66 | - 67 | test('quick action: Library navigates to marketplace', async ({ page }) => { - 68 | await page.goto(BASE + '/'); - 69 | await page.getByRole('button', { name: 'Library' }).click(); - 70 | await expect(page).toHaveURL(/\/library/); - 71 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 72 | }); - 73 | - 74 | test('New Drawing opens template picker', async ({ page }) => { - 75 | await page.goto(BASE + '/'); - 76 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 77 | await expect(page.getByRole('dialog')).toBeVisible(); - 78 | await expect(page.getByRole('heading', { name: 'Choose a Template' })).toBeVisible(); - 79 | await expect(page.getByRole('button', { name: 'Blank Canvas' })).toBeVisible(); - 80 | await expect(page.getByRole('button', { name: 'To-Do List' })).toBeVisible(); - 81 | await expect(page.getByRole('button', { name: 'Checklist' })).toBeVisible(); - 82 | await expect(page.getByRole('button', { name: 'Bullet List' })).toBeVisible(); - 83 | await expect(page.getByRole('button', { name: 'Flow Chart' })).toBeVisible(); - 84 | }); - 85 | }); - 86 | - 87 | // Projects / FileBrowser - 88 | test.describe.serial('projects', () => { - 89 | test.use({ storageState: 'playwright/.auth/state.json' }); - 90 | - 91 | test('shows Projects label in sidebar and breadcrumb', async ({ page }) => { - 92 | await page.goto(BASE + '/files'); - 93 | await expect(page.getByRole('navigation', { name: 'Main navigation' }).getByText('Projects')).toBeVisible(); - 94 | await expect(page.getByText('All Projects')).toBeVisible(); - 95 | }); - 96 | - 97 | test('can create a drawing from file browser', async ({ page }) => { - 98 | await page.goto(BASE + '/files'); - 99 | await page.getByRole('button', { name: 'Create new drawing' }).click(); - 100 | await expect(page.getByRole('dialog')).toBeVisible(); - 101 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 102 | await expect(page).toHaveURL(/\/drawing\//); - 103 | await expect(page.getByText('Loading Excalidraw')).toBeVisible(); - 104 | }); - 105 | }); - 106 | - 107 | // Editor / Canvas - 108 | test.describe.serial('editor', () => { - 109 | test.use({ storageState: 'playwright/.auth/state.json' }); - 110 | - 111 | test('creates drawing with To-Do template', async ({ page }) => { - 112 | await page.goto(BASE + '/'); - 113 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 114 | await page.getByRole('button', { name: 'To-Do List' }).click(); - 115 | await expect(page).toHaveURL(/\/drawing\//); - 116 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 117 | }); - 118 | - 119 | test('editor shows save controls and back button', async ({ page }) => { - 120 | await page.goto(BASE + '/'); - 121 | await page.getByRole('button', { name: 'New Drawing' }).click(); - 122 | await page.getByRole('button', { name: 'Blank Canvas' }).click(); - 123 | await expect(page).toHaveURL(/\/drawing\//); - 124 | await expect(page.getByRole('button', { name: /Save Now/i })).toBeVisible({ timeout: 10000 }); - 125 | await expect(page.getByRole('button', { name: /Back/i })).toBeVisible(); - 126 | }); - 127 | }); - 128 | - 129 | // Library Marketplace - 130 | test.describe.serial('library', () => { - 131 | test.use({ storageState: 'playwright/.auth/state.json' }); - 132 | - 133 | test('loads marketplace with search and categories', async ({ page }) => { - 134 | await page.goto(BASE + '/library'); - 135 | await expect(page.getByRole('heading', { name: 'Library Marketplace' })).toBeVisible(); - 136 | await expect(page.getByPlaceholder('Search libraries...')).toBeVisible(); - 137 | await expect(page.getByRole('button', { name: 'All' }).first()).toBeVisible(); - 138 | await expect(page.getByRole('button', { name: 'Open External' })).toBeVisible(); - 139 | }); - 140 | - 141 | test('search filters libraries', async ({ page }) => { - 142 | await page.goto(BASE + '/library'); - 143 | await page.getByPlaceholder('Search libraries...').fill('zzzznonexistent'); - 144 | await expect(page.getByText('No libraries found')).toBeVisible(); - 145 | }); - 146 | }); - 147 | - 148 | // Team / Invites - 149 | test.describe.serial('team', () => { - 150 | test.use({ storageState: 'playwright/.auth/state.json' }); - 151 | -> 152 | test('shows owner in members list', async ({ page }) => { - | ^ Error: Error reading storage state from playwright/.auth/state.json: - 153 | await page.goto(BASE + '/team'); - 154 | await expect(page.getByRole('heading', { name: 'Team Settings' })).toBeVisible(); - 155 | await expect(page.getByText('E2E User')).toBeVisible(); - 156 | await expect(page.getByText('owner')).toBeVisible(); - 157 | }); - 158 | - 159 | test('can send team invite', async ({ page }) => { - 160 | await page.goto(BASE + '/team'); - 161 | await page.getByLabel('Email address').fill('invited@test.com'); - 162 | await page.locator('select').selectOption('editor'); - 163 | await page.getByRole('button', { name: 'Send Invite' }).click(); - 164 | await expect(page.getByText('Invite sent!')).toBeVisible(); - 165 | await expect(page.getByText('Pending Invites')).toBeVisible(); - 166 | await expect(page.getByText('invited@test.com')).toBeVisible(); - 167 | await expect(page.getByText('editor').first()).toBeVisible(); - 168 | }); - 169 | }); - 170 | -``` \ No newline at end of file