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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jank",
"version": "26.15.2",
"version": "26.15.3",
"description": "A TweetDeck-style multi-column deck for Nostr — just another nostr klient",
"type": "module",
"author": "DocNR",
Expand Down
45 changes: 3 additions & 42 deletions src/components/Column/ColumnHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useUserPreferences } from '@/providers/UserPreferencesProvider'
import { TColumn } from '@/types/column'
import { notificationUnreadCountAtom } from '@/atoms/notification-unread-count'
import { useAtomValue } from 'jotai'
import { Pin, PinOff, X } from 'lucide-react'
import { X } from 'lucide-react'
import { HTMLAttributes, MouseEvent as ReactMouseEvent, memo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ColumnHeaderMenu from './ColumnHeaderMenu'
Expand All @@ -20,20 +20,16 @@ import { signingState } from './signing-state'
/**
* Column header. Adaptive layout:
* - matched / view-only columns: a single row —
* avatar · label · [signing chip] · ——— · 📌 · ⋯ · ✕
* avatar · label · [signing chip] · ——— · ⋯ · ✕
* - mismatched ("loud") columns: two rows — row 1 carries identity + controls,
* row 2 is the full-width "Acting as" signing indicator so it can never be
* squeezed or clipped.
*
* Pin/unpin stays an inline button (the icon is the pin-state indicator —
* lit when pinned, muted when transient). Compose + the list-style toggle
* live in the ⋯ ColumnHeaderMenu.
* Compose + the list-style toggle live in the ⋯ ColumnHeaderMenu.
*/
const ColumnHeader = memo(function ColumnHeader({
column,
onRemove,
onPin,
onUnpin,
onScrollToTop,
dragHandleProps,
isFocused,
Expand All @@ -44,8 +40,6 @@ const ColumnHeader = memo(function ColumnHeader({
}: {
column: TColumn
onRemove: (id: string) => void
onPin: (id: string) => void
onUnpin: (id: string) => void
/** Scrolls the column body to the top. Fired on a plain title-bar click
* (not on the inner controls or a drag). */
onScrollToTop: () => void
Expand Down Expand Up @@ -145,39 +139,6 @@ const ColumnHeader = memo(function ColumnHeader({
)}
</div>
<div className="flex shrink-0 items-center gap-1">
{column.transient ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-muted-foreground hover:text-foreground size-9 sm:size-7"
aria-label={t('Pin column')}
onPointerDown={(e) => e.stopPropagation()}
onClick={() => onPin(column.id)}
>
<Pin className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t('Pin column')}</TooltipContent>
</Tooltip>
) : (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-primary size-9 sm:size-7"
aria-label={t('Unpin column')}
onPointerDown={(e) => e.stopPropagation()}
onClick={() => onUnpin(column.id)}
>
<PinOff className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{t('Unpin column')}</TooltipContent>
</Tooltip>
)}
{showMenu && (
<ColumnHeaderMenu
showCompose={showCompose}
Expand Down
4 changes: 1 addition & 3 deletions src/components/Column/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function Column({ column, dragHandleProps, style }: Props) {
// A per-column `column.width` override still wins when present.
const baseWidth: number | string = column.width ?? 'var(--deck-col-width, 400px)'
const { isSmallScreen } = useScreenSize()
const { pinColumn, unpinColumn, removeColumn, removingIds, updateColumnConfig } = useColumns()
const { removeColumn, removingIds, updateColumnConfig } = useColumns()
const { notificationListStyle: globalListStyle } = useUserPreferences()
const { pubkey: activeAccountPubkey } = useNostr()
const setActiveColumnId = useSetAtom(activeColumnIdAtom)
Expand Down Expand Up @@ -305,8 +305,6 @@ export function Column({ column, dragHandleProps, style }: Props) {
<ColumnHeader
column={column}
onRemove={handleHeaderRemove}
onPin={pinColumn}
onUnpin={unpinColumn}
onScrollToTop={handleScrollToTop}
dragHandleProps={dragHandleProps}
isFocused={isFocused}
Expand Down
40 changes: 5 additions & 35 deletions src/components/CommandPalette/StarterCommands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ export default function StarterCommands() {
const {
columns,
focusOrCreateColumn,
pinColumn,
unpinColumn,
removeColumn,
closeAllUnpinned,
closeAllTransient,
reorderColumns
} = useColumns()
const { account, switchAccount, setActivePubkey } = useNostr()
Expand Down Expand Up @@ -241,37 +239,11 @@ export default function StarterCommands() {
}
})
list.push({
id: 'column.pin',
label: t('Pin column'),
group: 'columns',
condition: () => {
const a = getActive()
return !!a && a.transient === true
},
run: () => {
const a = getActive()
if (a) pinColumn(a.id)
}
})
list.push({
id: 'column.unpin',
label: t('Unpin column'),
group: 'columns',
condition: () => {
const a = getActive()
return !!a && a.transient !== true
},
run: () => {
const a = getActive()
if (a) unpinColumn(a.id)
}
})
list.push({
id: 'column.closeAllUnpinned',
label: t('Close all unpinned columns'),
id: 'column.closeAllTransient',
label: t('Close all temporary columns'),
group: 'columns',
condition: () => columns.some((c) => c.transient === true),
run: () => closeAllUnpinned()
run: () => closeAllTransient()
})
list.push({
id: 'column.moveLeft',
Expand Down Expand Up @@ -489,10 +461,8 @@ export default function StarterCommands() {
accounts,
account?.pubkey,
profileByPubkey,
pinColumn,
unpinColumn,
removeColumn,
closeAllUnpinned,
closeAllTransient,
reorderColumns,
focusOrCreateColumn,
switchAccount,
Expand Down
2 changes: 1 addition & 1 deletion src/components/DeckArea/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export default function DeckArea() {
if (activeColumnId !== null) setActiveColumnId(null)
// Defensive: the modal-split rule wraps `column.close` to exit beam
// before removing, so single-column close lands here with beam already
// OFF. Catches bulk-close paths (closeAllUnpinned) and any future
// OFF. Catches bulk-close paths (closeAllTransient) and any future
// out-of-band removal.
if (focusBeamActive) setFocusBeamActive(false)
return
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,7 @@ export default {
'Your agent never gets your signing key — your signer still gates every event you publish. {{brand}} must stay open in a browser tab for your agent to work.':
'Your agent never gets your signing key — your signer still gates every event you publish. {{brand}} must stay open in a browser tab for your agent to work.',
'Columns in this deck that view your other paired {{brand}} accounts (their npubs become visible to the agent)':
'Columns in this deck that view your other paired {{brand}} accounts (their npubs become visible to the agent)'
'Columns in this deck that view your other paired {{brand}} accounts (their npubs become visible to the agent)',
'Close all temporary columns': 'Close all temporary columns'
}
}
43 changes: 5 additions & 38 deletions src/providers/ColumnsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,15 @@ type TColumnsContext = {
source: { viewContext: string; signingIdentity: string | null } | null,
parentColumnId?: string
) => void
/** Promote a transient column to persisted (transient: false), then write through to localStorage. */
pinColumn: (id: string) => void
/** Demote a pinned column to transient (transient: true). Drops it from the persisted set without removing it from the live deck. */
unpinColumn: (id: string) => void
/**
* Remove any column (transient or persisted). Animates: marks the id
* as removing for 280ms (column-fade-out keyframe) before the actual
* filter+persist. No-ops on a second call for the same id while it's
* already in `removingIds`.
*/
removeColumn: (id: string) => void
/** Remove every column where transient === true. Pinned columns are untouched. */
closeAllUnpinned: () => void
/** Remove every column where transient === true. Permanent columns are untouched. */
closeAllTransient: () => void
/**
* Patch a column's `config` object in-place (shallow merge). Persists
* immediately. Used today by the per-column notification list-style
Expand Down Expand Up @@ -810,31 +806,6 @@ export function ColumnsProvider({ children }: { children: ReactNode }) {
[transientColumnMode, account?.pubkey, requestFocusedColumn]
)

const pinColumn = useCallback(
(id: string) => {
setColumns((prev) => {
const next = prev.map((c) => (c.id === id ? { ...c, transient: false } : c))
persist(next)
return next
})
},
[persist]
)

const unpinColumn = useCallback(
(id: string) => {
setColumns((prev) => {
const next = prev.map((c) => (c.id === id ? { ...c, transient: true } : c))
// Persist drops `transient: true` entries, so demoting a column writes
// through to the persisted set as "removed". The column stays in the
// live deck until the user explicitly closes it (or reloads).
persist(next)
return next
})
},
[persist]
)

// 280ms matches the `column-fade-out` keyframe in src/index.css. Holding
// the id in `removingIds` for the duration of the animation is what makes
// the keyboard shortcut animate identically to the mouse X click — both
Expand Down Expand Up @@ -871,7 +842,7 @@ export function ColumnsProvider({ children }: { children: ReactNode }) {
[persist]
)

const closeAllUnpinned = useCallback(() => {
const closeAllTransient = useCallback(() => {
setColumns((prev) => {
const next = prev.filter((c) => !c.transient)
persist(next)
Expand Down Expand Up @@ -1185,10 +1156,8 @@ export function ColumnsProvider({ children }: { children: ReactNode }) {
addColumn,
focusOrCreateColumn,
addTransientColumn,
pinColumn,
unpinColumn,
removeColumn,
closeAllUnpinned,
closeAllTransient,
updateColumnConfig,
closeColumnsForAccount,
reorderColumns,
Expand Down Expand Up @@ -1220,10 +1189,8 @@ export function ColumnsProvider({ children }: { children: ReactNode }) {
addColumn,
focusOrCreateColumn,
addTransientColumn,
pinColumn,
unpinColumn,
removeColumn,
closeAllUnpinned,
closeAllTransient,
updateColumnConfig,
v2Decks,
activeDeck,
Expand Down
7 changes: 7 additions & 0 deletions src/release-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export type ReleaseNote = {
}

export const RELEASE_NOTES: ReleaseNote[] = [
{
version: '26.15.3',
date: '2026-06-17',
highlights: [
'Columns you open by clicking are now always temporary and clear when you reload; the per-column pin button is gone. Use the + button to add the columns you want to keep.'
]
},
{
version: '26.15.2',
date: '2026-06-16',
Expand Down
Loading