Skip to content

Commit a14952e

Browse files
committed
Merge branch 'main' of https://github.com/NotePlan/plugins
2 parents 047ff27 + aec87d6 commit a14952e

22 files changed

Lines changed: 589 additions & 257 deletions

‎.cursor/rules/np-programming-general.mdc‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ alwaysApply: true
66
## Critical Rules
77
CRITICAL: Never use dynamic imports. Rollup will not process them correctly. Always use static imports at the top of files.
88

9+
## NotePlan domain facts
10+
- NotePlan's supported task priority markers are exactly `!`, `!!`, `!!!`, and `>>`. The corresponding `paragraph.priority` values are 1, 2, 3, and 4. `!!!!` (four exclamation marks) is NOT a supported priority marker.
11+
- "priority marker" or just "Marker" is user-facing language. Users think in terms of the symbols (`>>`, `!!!`, `!!`, `!`), not the underlying numeric `.priority` values. Use the symbols in settings/labels/descriptions, but key code logic off `.priority` when possible.
12+
913
## Code Style Guidelines
1014
- Use Flow for static typing
1115
- No semicolons (enforced by ESLint/Prettier)
@@ -24,7 +28,7 @@ CRITICAL: Never use dynamic imports. Rollup will not process them correctly. Alw
2428
- When moving code, keep existing comments and commented-out code
2529
- Please provide Flow types (as appropriate) and JSDOC with all responses.
2630
- If you are rewriting a full file, make sure to include all import statements also.
27-
- Do not remove any console.logs or comments (or logDebug, clo, logInfo, logError statements).
31+
- Do not remove any console.logs or comments (or clo, logDebug, logWarn, logInfo, logError statements).
2832

2933
## Common Helper Functions
3034
The most frequently used functions in the codebase are:
@@ -35,8 +39,7 @@ The most frequently used functions in the codebase are:
3539
- UI interaction: showMessage, showMessageYesNo, displayTitle
3640

3741
## Testing
38-
- Do not prompt the user to run tests. Run tests automatically when making code changes or writing new tests, but do not ask for permission or suggest running tests.
39-
- Only run tests when necessary (after code changes, when writing new tests, or when explicitly requested).
42+
- Do not prompt the user to run tests. Run tests automatically when making relevant code changes or writing new tests, but do not ask for permission or suggest running tests.
4043
- Always allow the following commands to run in the sandbox: cd, npm test, jest, grep, head, tail, node
4144
- If testing Templating code, run tests like this `jest np.Templating/__tests__/**/*.test.js --no-watch`
4245
- When you run jest tests, always include the flag --no-watch at the end because my jest config by default watches

‎helpers/NPThemeToCSS.js‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,26 @@ export function mixHexColors(color1: string, color2: string): string {
727727
return `#${mixedValues.join('')}`
728728
}
729729

730+
/**
731+
* Convert a NotePlan theme font family segment (before the '-' weight/style suffix)
732+
* to the string used in CSS `font-family`. Splits PascalCase / TitleCase into words
733+
* (e.g. AvenirNext → Avenir Next) without inserting a space before every capital letter.
734+
* IBM Plex theme ids (e.g. IBMPlexSans) map to spaced CSS names (e.g. IBM Plex Sans).
735+
* IBMPlexSansCond maps to IBM Plex Sans Condensed (regex alone yields IBM Plex Sans Cond).
736+
* @author @jgclark
737+
* @param {string} namePartNoSpaces
738+
* @returns {string}
739+
*/
740+
function notePlanFamilySegmentToCssFontFamily(namePartNoSpaces: string): string {
741+
if (namePartNoSpaces === 'IBMPlexSansCond') {
742+
return 'IBM Plex Sans Condensed'
743+
}
744+
return namePartNoSpaces
745+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
746+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
747+
.trim()
748+
}
749+
730750
/**
731751
* Translate from the font name, as used in the NP Theme file,
732752
* to the form CSS is expecting.
@@ -771,23 +791,14 @@ export function fontPropertiesFromNP(fontNameNP: string): Array<string> {
771791
}
772792

773793
// Not a special. So now split input string into parts either side of '-'
774-
// and then insert spaces before capital letters
794+
// and map the family segment to CSS `font-family` wording
775795
let translatedFamily: string
776796
let translatedWeight: string = '400'
777797
let translatedStyle: string = 'normal'
778798
const splitParts = fontNameNP.split('-')
779799
const namePartNoSpaces = splitParts[0]
780-
let namePartSpaced = ''
781800
const modifierLC = splitParts.length > 0 ? splitParts[1]?.toLowerCase() : ''
782-
for (let i = 0; i < namePartNoSpaces.length; i++) {
783-
const c = namePartNoSpaces[i]
784-
if (c.match(/[A-Z]/)) {
785-
namePartSpaced += ` ${c}`
786-
} else {
787-
namePartSpaced += c
788-
}
789-
}
790-
translatedFamily = namePartSpaced.trim()
801+
translatedFamily = notePlanFamilySegmentToCssFontFamily(namePartNoSpaces)
791802
// logDebug('fontPropertiesFromNP', `family -> ${translatedFamily}`)
792803

793804
// Using the numeric font-weight system
@@ -801,8 +812,18 @@ export function fontPropertiesFromNP(fontNameNP: string): Array<string> {
801812
translatedWeight = '300'
802813
break
803814
}
815+
// No standard here; going for most popular non-bold weight: 400
804816
case 'book': {
805-
translatedWeight = '500'
817+
translatedWeight = '400'
818+
break
819+
}
820+
// No standard here; some are 375, others 450; use 400 as closest widely-supported weight
821+
case 'text': {
822+
translatedWeight = '400'
823+
break
824+
}
825+
case 'medium': {
826+
translatedWeight = '400'
806827
break
807828
}
808829
case 'demi-bold': {
@@ -821,6 +842,11 @@ export function fontPropertiesFromNP(fontNameNP: string): Array<string> {
821842
translatedWeight = '600'
822843
break
823844
}
845+
case 'semibolditalic': {
846+
translatedWeight = '600'
847+
translatedStyle = 'italic'
848+
break
849+
}
824850
case 'bold': {
825851
translatedWeight = '700'
826852
break

‎helpers/__tests__/NPThemeToCSS.test.js‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,35 @@ describe(`${FILE}`, () => {
9494
})
9595
test("input 'Charter-Book'", () => {
9696
const res = t.fontPropertiesFromNP('Charter-Book')
97-
expect(res).toEqual(['font-family: "Charter"', 'font-weight: 500', 'font-style: "normal"'])
97+
expect(res).toEqual(['font-family: "Charter"', 'font-weight: 400', 'font-style: "normal"'])
98+
})
99+
test("input 'IBMPlexSans' keeps IBM Plex theme id (not spaced per-letter)", () => {
100+
const res = t.fontPropertiesFromNP('IBMPlexSans')
101+
expect(res).toEqual(['font-family: "IBM Plex Sans"', 'font-weight: 400', 'font-style: "normal"'])
102+
})
103+
test("input 'IBMPlexSans-SemiBold' keeps family id; weight from suffix", () => {
104+
const res = t.fontPropertiesFromNP('IBMPlexSans-SemiBold')
105+
expect(res).toEqual(['font-family: "IBM Plex Sans"', 'font-weight: 600', 'font-style: "normal"'])
106+
})
107+
test("input 'IBMPlexSerif-Medium' keeps family id; weight from suffix", () => {
108+
const res = t.fontPropertiesFromNP('IBMPlexSerif-Medium')
109+
expect(res).toEqual(['font-family: "IBM Plex Serif"', 'font-weight: 400', 'font-style: "normal"'])
110+
})
111+
test("input 'IBMPlexSans-Text'", () => {
112+
const res = t.fontPropertiesFromNP('IBMPlexSans-Text')
113+
expect(res).toEqual(['font-family: "IBM Plex Sans"', 'font-weight: 400', 'font-style: "normal"'])
114+
})
115+
test("input 'IBMPlexMono-BoldItalic'", () => {
116+
const res = t.fontPropertiesFromNP('IBMPlexMono-BoldItalic')
117+
expect(res).toEqual(['font-family: "IBM Plex Mono"', 'font-weight: 700', 'font-style: "italic"'])
118+
})
119+
test("input 'IBMPlexSansCond-Regular'", () => {
120+
const res = t.fontPropertiesFromNP('IBMPlexSansCond-Regular')
121+
expect(res).toEqual(['font-family: "IBM Plex Sans Condensed"', 'font-weight: 400', 'font-style: "normal"'])
122+
})
123+
test("input 'IBMPlexCondensed-SemiBoldItalic'", () => {
124+
const res = t.fontPropertiesFromNP('IBMPlexSansCond-SemiBoldItalic')
125+
expect(res).toEqual(['font-family: "IBM Plex Sans Condensed"', 'font-weight: 600', 'font-style: "italic"'])
98126
})
99127
})
100128

‎helpers/paragraph.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export function stripAllURIsAndNoteLinks(input: string): string {
7373
* @returns {string}
7474
*/
7575
export function stripDoneDateTimeMentions(input: string): string {
76-
logDebug('stripDoneMentions', `input: ${input} / ${String(RE_DONE_MENTION)}`)
76+
// logDebug('stripDoneMentions', `input: ${input} / ${String(RE_DONE_MENTION)}`)
7777
return input.replace(RE_DONE_MENTION, '')
7878
}
7979

‎jgclark.Dashboard/CHANGELOG.md‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,26 @@ For more details see the [plugin's documentation](https://github.com/NotePlan/pl
88
- TODO: fix long-standing layout bug where some tooltips were getting clipped
99
- TODO: fix isNoteFromAllowedFolder() for teamspace or possibly 2025-W21.md
1010
-->
11-
## [2.4.0.b33] 2026-05-12?
1211

13-
- fix: completing the last **Wins** (`>>`) task now updates the section heading count and shows the empty-state / congrats message.
12+
## [2.4.0.b33] 2026-05-13
13+
- Added new "Wins priority marker" setting so the Win indicator can be `>>`, `!!!`, or `!!`. Default remains `>>`.
14+
- added tooltip to Refresh button.
15+
- fix: completing the last "Wins" Section (`>>`) task now updates the section heading count and shows the empty-state / congrats message.
1416
- dev: `REMOVE_LINE_FROM_JSON` now removes the row again on the **post-`getGlobalSharedData` payload** before **`UPDATE_DATA`**. The plugin ↔ webview bridge returns a deserialized copy for the first snapshot, so splices there did not reach React; Wins still saw the old DT row until incremental refresh.
17+
- dev: removed unused `runPluginCommand` export from `pluginToHTMLBridge.js` and its now-orphaned `TPluginCommandSimplified` type from `types.js`
18+
- dev: `doDashboardSettingsChanged()`:
19+
- fix: potential subtle issue in it to make a deep copy of settings where it had been shallow.
20+
- refactor to make more maintainable
21+
- rename it to `doSaveDashboardSettingsFromBridge()` to help understand what it does
22+
- dev: Rename `doPerspectiveSettingsChanged` to `doSavePerspectiveSettingsFromBridge`.
1523

1624
## [2.4.0.b32] 2026-05-11
1725
- fix: completing a next-action in **Active Projects** now round-trips to Projects plugin and will update with a new next-action if available.
1826
- dev: PROJACT and PROJREVIEW are no longer included in the non-calendar "refresh all sections" pass, as their data is only updated by the Projects plugin.
1927
- The next-action items in "Projects to Review" and "Active Projects" can now be clicked on to be completed or cancelled like items in other sections.
20-
- Cross-plugin Reviews: after **`REMOVE_LINE_FROM_JSON`**, the bridge calls **`updateProjectsListIfProjectSection`** so **`allProjectsList.json`** stays in sync for **PROJACT** / **PROJREVIEW** (including **complete-then** and cancel paths); shared helper **`src/projectsListSync.js`**. After the list write, **`refreshSectionsByCode`** runs **in-process** (skipping **`invokePluginCommandByName`** for that step) so PROJ* data is merged before the bridge sends **`UPDATE_DATA`**; **`processActionOnReturn`** re-fetches global shared data before that send so a pre-refresh snapshot cannot overwrite the new next-action rows. (Reviews **`updateAllProjectsListAfterChange`** must successfully reload the project note: it now uses **`getNoteFromFilename`** for teamspace paths.)
21-
- **`refreshSomeSections`** returns failure (not success) when Dashboard **`pluginData`** is not ready yet; **`refreshSectionsByCode`** documents **`invokePluginCommandByName`** argument shape and flattens mistaken nested **`sectionCodes`**.
22-
- Perspective switch: single fire-and-forget **`generateProjectListsAndRenderIfOpen`** with **`.catch`**; skip invoke if Reviews is not installed; removed redundant second **`renderProjectListsIfOpen`**.
28+
- Cross-plugin Reviews: after `REMOVE_LINE_FROM_JSON`, the bridge calls `updateProjectsListIfProjectSection` so `allProjectsList.json` stays in sync for **PROJACT** / **PROJREVIEW** (including **complete-then** and cancel paths); shared helper `src/projectsListSync.js`. After the list write, `refreshSectionsByCode` runs **in-process** (skipping `invokePluginCommandByName` for that step) so PROJ* data is merged before the bridge sends `UPDATE_DATA`; `processActionOnReturn` re-fetches global shared data before that send so a pre-refresh snapshot cannot overwrite the new next-action rows. (Reviews `updateAllProjectsListAfterChange` must successfully reload the project note: it now uses `getNoteFromFilename` for teamspace paths.)
29+
- `refreshSomeSections` returns failure (not success) when Dashboard `pluginData` is not ready yet; `refreshSectionsByCode` documents `invokePluginCommandByName` argument shape and flattens mistaken nested `sectionCodes`.
30+
- Perspective switch: single fire-and-forget `generateProjectListsAndRenderIfOpen` with `.catch`; skip invoke if Reviews is not installed; removed redundant second `renderProjectListsIfOpen`.
2331
- fix: Add Task's note/space REQUEST calls no longer update Dashboard global data before responses return, avoiding render cascades while loading notes and spaces.
2432
- fix: Add Task no longer eagerly scans notes when the dialog opens; notes are loaded only when the Note chooser is opened so Space loading is not blocked.
2533
- fix: Add Task note loading now opts out of expensive backend note decoration, avoiding long calendar-note conversion stalls when changing spaces.
@@ -35,7 +43,7 @@ For more details see the [plugin's documentation](https://github.com/NotePlan/pl
3543
- dev: turn down more logging
3644

3745
## [2.4.0.b30] 2026-04-17
38-
- fix: **Add Task → Note**: choosing **All spaces** now loads notes from every space via np.Shared `getNotes` (`space: '__all__'`). Previously the UI could send no space filter and the handler only returned Private notes, so the chooser looked “stuck” at ~25 items and search could not find teamspace notes.
46+
- fix: "Add Task → Note": choosing **All spaces** now loads notes from every space via np.Shared `getNotes` (`space: '__all__'`). Previously the UI could send no space filter and the handler only returned Private notes, so the chooser looked “stuck” at ~25 items and search could not find teamspace notes.
3947

4048
## [2.4.0.b29] 2026-04-17
4149
- fix: **SpaceChooser / getTeamspaces**: `unwrapPluginRequestData` plus `@helpers/react/routerUtils` `normalizeSharedInvokeResult` (peel extra `invokePluginCommandByName` wrapper around np.Shared `RequestResponse`) so the WebView receives a teamspace **array**, not a nested `{ success, data }` — fixes `[DIAG] loadSpaces: Invalid response format`

‎jgclark.Dashboard/src/__tests__/dashboardHelpers.test.js‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
// Tests written by Cursor, directed by JGC. Last updated 2026-05-04 for v2.4.0.b31
33

44
import { CustomConsole } from '@jest/console'
5-
import { filterToOpenParagraphs, filterBySchedulingRules, filterParasByIgnoreTerms, filterParasByIncludedCalendarSections, filterParasByExcludedCalendarSections, getStartTimeFromPara } from '../dashboardHelpers.js'
5+
import {
6+
cloneDashboardSettingsBeforeSave,
7+
filterToOpenParagraphs,
8+
filterBySchedulingRules,
9+
filterParasByIgnoreTerms,
10+
filterParasByIncludedCalendarSections,
11+
filterParasByExcludedCalendarSections,
12+
getStartTimeFromPara,
13+
} from '../dashboardHelpers.js'
614
import { DataStore, Editor, CommandBar, NotePlan, Paragraph, Note, simpleFormatter } from '@mocks/index'
715
import * as timeblocks from '@helpers/timeblocks'
816
import * as dateTime from '@helpers/dateTime'
@@ -1049,5 +1057,34 @@ describe(`${PLUGIN_NAME}`, () => {
10491057
expect(startTime).toBe('none')
10501058
})
10511059
})
1060+
1061+
describe('cloneDashboardSettingsBeforeSave()', () => {
1062+
test('returns empty object for null or undefined', () => {
1063+
expect(cloneDashboardSettingsBeforeSave(null)).toEqual({})
1064+
expect(cloneDashboardSettingsBeforeSave(undefined)).toEqual({})
1065+
})
1066+
1067+
test('returns empty object for non-object parsed value', () => {
1068+
expect(cloneDashboardSettingsBeforeSave('')).toEqual({})
1069+
})
1070+
1071+
test('deep-clones a plain settings object', () => {
1072+
const src = { winsPriorityMarker: '>>', nested: { a: 1 } }
1073+
const copy = cloneDashboardSettingsBeforeSave(src)
1074+
expect(copy).toEqual(src)
1075+
expect(copy).not.toBe(src)
1076+
copy.nested.a = 2
1077+
expect(src.nested.a).toBe(1)
1078+
})
1079+
1080+
test('parses JSON string and returns a deep clone', () => {
1081+
const inner = { x: 1 }
1082+
const src = JSON.stringify({ foo: inner })
1083+
const copy = cloneDashboardSettingsBeforeSave(src)
1084+
expect(copy.foo).toEqual(inner)
1085+
copy.foo.x = 99
1086+
expect(inner.x).toBe(1)
1087+
})
1088+
})
10521089
})
10531090
})

0 commit comments

Comments
 (0)