diff --git a/src/components/Editor.css b/src/components/Editor.css
index 8791d5c5..9eab50e5 100644
--- a/src/components/Editor.css
+++ b/src/components/Editor.css
@@ -179,6 +179,13 @@
flex-shrink: 0;
}
+.title-section__row {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ padding-top: 16px;
+}
+
.title-section__separator {
border-bottom: 1px solid var(--border-primary, rgba(0, 0, 0, 0.08));
margin-top: 12px;
@@ -186,7 +193,6 @@
/* --- Note Icon Area --- */
.note-icon-area {
- padding: 16px 0 0;
flex-shrink: 0;
}
@@ -203,8 +209,10 @@
}
.note-icon-button--active {
- font-size: 40px;
+ font-size: 32px;
+ line-height: 1.2;
transition: transform 0.1s;
+ margin-right: 8px;
}
.note-icon-button--active:hover:not(:disabled) {
@@ -272,8 +280,8 @@
/* --- Title Field --- */
.title-field {
- padding: 4px 0 0;
- flex-shrink: 0;
+ flex: 1;
+ min-width: 0;
}
.title-field__input {
@@ -282,13 +290,12 @@
border: none;
outline: none;
background: transparent;
- font-size: var(--headings-h1-font-size);
- font-weight: var(--headings-h1-font-weight);
- line-height: var(--headings-h1-line-height);
- letter-spacing: var(--headings-h1-letter-spacing);
+ font-size: var(--headings-h1-font-size, 28px);
+ font-weight: var(--headings-h1-font-weight, 700);
+ line-height: var(--headings-h1-line-height, 1.2);
+ letter-spacing: var(--headings-h1-letter-spacing, -0.015em);
color: var(--foreground);
padding: 0;
- margin-left: 8px;
}
.title-field__input::placeholder {
diff --git a/src/components/EditorContent.tsx b/src/components/EditorContent.tsx
index afc1e5b2..a2076e46 100644
--- a/src/components/EditorContent.tsx
+++ b/src/components/EditorContent.tsx
@@ -203,18 +203,20 @@ export function EditorContent({
{showEditor && activeTab && (
-
-
onTitleChange?.(activeTab.entry.path, newTitle)}
- />
+
+
+ onTitleChange?.(activeTab.entry.path, newTitle)}
+ />
+
diff --git a/tests/smoke/title-emoji-inline.spec.ts b/tests/smoke/title-emoji-inline.spec.ts
new file mode 100644
index 00000000..95f04d25
--- /dev/null
+++ b/tests/smoke/title-emoji-inline.spec.ts
@@ -0,0 +1,97 @@
+import { test, expect } from '@playwright/test'
+
+test.describe('Title H1 with inline emoji', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto('/')
+ await page.waitForTimeout(500)
+
+ // Open a note so TitleField is visible
+ const noteItem = page.locator('.app__note-list .cursor-pointer').first()
+ await noteItem.click()
+ await page.waitForTimeout(500)
+ })
+
+ test('title-section__row is a horizontal flex container', async ({ page }) => {
+ const row = page.locator('.title-section__row')
+ await expect(row).toBeVisible({ timeout: 3000 })
+
+ const display = await row.evaluate(el => getComputedStyle(el).display)
+ const flexDir = await row.evaluate(el => getComputedStyle(el).flexDirection)
+ expect(display).toBe('flex')
+ expect(flexDir).toBe('row')
+ })
+
+ test('title field renders with large H1 font size', async ({ page }) => {
+ const input = page.locator('.title-field__input')
+ await expect(input).toBeVisible({ timeout: 3000 })
+
+ const fontSize = await input.evaluate(el => {
+ const computed = getComputedStyle(el)
+ return parseFloat(computed.fontSize)
+ })
+ // Title must be at least 24px (H1 style, significantly larger than body ~16px)
+ expect(fontSize).toBeGreaterThanOrEqual(24)
+ })
+
+ test('title field renders with bold font weight', async ({ page }) => {
+ const input = page.locator('.title-field__input')
+ await expect(input).toBeVisible({ timeout: 3000 })
+
+ const fontWeight = await input.evaluate(el => {
+ const computed = getComputedStyle(el)
+ return parseInt(computed.fontWeight, 10)
+ })
+ // Font weight 700+ (bold or heavier)
+ expect(fontWeight).toBeGreaterThanOrEqual(700)
+ })
+
+ test('emoji icon and title are on the same horizontal line when icon present', async ({ page }) => {
+ // Add an icon via the "Add icon" button
+ const iconArea = page.locator('[data-testid="note-icon-area"]')
+ await iconArea.hover()
+ await page.waitForTimeout(200)
+
+ const addBtn = page.locator('[data-testid="note-icon-add"]')
+ if (await addBtn.isVisible()) {
+ await addBtn.click()
+ await page.waitForTimeout(300)
+
+ // Pick the first emoji in the picker
+ const emojiBtn = page.locator('.emoji-picker-grid button').first()
+ if (await emojiBtn.isVisible()) {
+ await emojiBtn.click()
+ await page.waitForTimeout(300)
+ }
+ }
+
+ const iconDisplay = page.locator('[data-testid="note-icon-display"]')
+ const titleInput = page.locator('[data-testid="title-field-input"]')
+
+ if (await iconDisplay.isVisible()) {
+ const iconBox = await iconDisplay.boundingBox()
+ const titleBox = await titleInput.boundingBox()
+
+ expect(iconBox).not.toBeNull()
+ expect(titleBox).not.toBeNull()
+
+ // Emoji and title must be on the same row: their vertical centers overlap
+ const iconCenter = iconBox!.y + iconBox!.height / 2
+ const titleTop = titleBox!.y
+ const titleBottom = titleBox!.y + titleBox!.height
+
+ expect(iconCenter).toBeGreaterThanOrEqual(titleTop - 8)
+ expect(iconCenter).toBeLessThanOrEqual(titleBottom + 8)
+
+ // Emoji must be to the left of the title
+ expect(iconBox!.x).toBeLessThan(titleBox!.x)
+ }
+ })
+
+ test('clicking title still enters edit mode', async ({ page }) => {
+ const titleInput = page.locator('[data-testid="title-field-input"]')
+ await expect(titleInput).toBeVisible({ timeout: 3000 })
+
+ await titleInput.click()
+ await expect(titleInput).toBeFocused()
+ })
+})