feat(projects): section headings you can count at a glance, and an Inbox that speaks the language
Two from use. Section headings were small uppercase text over a hairline — a heading you have to look for. A project with four sections is four groups, and the eye should be able to count them without reading a word, so the heading gets a ground of its own and the rows sit against the page. `--surface` rather than a tint of the accent: the colour on that page belongs to the project, and a section is not a second thing to colour. A step in lightness separates them in all five themes without anybody measuring anything. And the Inbox. The server names it at account creation, before anybody has seen a settings page, so it is the one project name nobody chose — and an account made in Danish went on saying "Indbakke" inside an English interface, which reads as a string somebody forgot to translate. It was. `projectName()` translates it while it still carries the name the server gave it; rename it and that is your name for it, in every language, like any other project. The rename field keeps the raw name, or editing a translated label would save the translation. The screenshot set gains the list view, which is where sections read as bands rather than as columns — and the shape most people actually work in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fe52818ab6
commit
19aa02e80c
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { app, sidebar } from '$lib/stores.svelte.js';
|
||||
import { app, sidebar, projectName } from '$lib/stores.svelte.js';
|
||||
import { api } from '$lib/api.js';
|
||||
import { TASK, PROJECT, GROUP, startDrag, carries, dragged, accept } from '$lib/dnd.js';
|
||||
import { COLORS, colorVar } from '$lib/colors.js';
|
||||
@@ -410,7 +410,7 @@
|
||||
ondrop={(e) => (carries(e, TASK) ? onTaskDrop(e, project) : sortable && onDrop(e, project))}
|
||||
>
|
||||
<span class="dot" style="background: {colorVar(project.color)}" aria-hidden="true"></span>
|
||||
{project.name}
|
||||
{projectName(project)}
|
||||
{#if project.shared}
|
||||
<span class="count">{project.member_count}</span>
|
||||
{/if}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* necessarily the last one sent.
|
||||
*/
|
||||
import { api, humanMessage } from '$lib/api.js';
|
||||
import { app } from '$lib/stores.svelte.js';
|
||||
import { app, projectName } from '$lib/stores.svelte.js';
|
||||
import { focusOnMount } from '$lib/focus.js';
|
||||
import { t } from '$lib/i18n.svelte.js';
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
<label for="project">{t('detail.project')}</label>
|
||||
<select id="project" bind:value={projectId} onchange={saveProject}>
|
||||
{#each app.projects as project (project.id)}
|
||||
<option value={project.id}>{project.name}</option>
|
||||
<option value={project.id}>{projectName(project)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -620,6 +620,28 @@ class AppState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* What to call a project on screen.
|
||||
*
|
||||
* Almost always its own name — somebody wrote it and it is theirs. The Inbox is the
|
||||
* exception: the server names it at account creation, before anybody has seen a
|
||||
* settings page, so it is the one project name nobody chose. Leaving it as stored
|
||||
* meant an account created in Danish said "Indbakke" in an English interface, which
|
||||
* reads as a string somebody forgot to translate. It was.
|
||||
*
|
||||
* Only while it still carries a name the server gave it. Rename it and that is your
|
||||
* name for it, in every language — the same rule as any other project.
|
||||
*/
|
||||
const SYSTEM_INBOX_NAMES = new Set(['indbakke', 'inbox']);
|
||||
|
||||
export function projectName(project) {
|
||||
if (!project) return '';
|
||||
if (project.is_inbox && SYSTEM_INBOX_NAMES.has(project.name.trim().toLowerCase())) {
|
||||
return t('nav.inbox');
|
||||
}
|
||||
return project.name;
|
||||
}
|
||||
|
||||
/** Enough of a task's title to recognise it, without a toast the width of the page. */
|
||||
function truncate(text, max = 40) {
|
||||
const trimmed = text.trim();
|
||||
|
||||
Reference in New Issue
Block a user