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
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ All notable changes to this project will be documented in this file.

- **Overlapping `withoutNotifying` calls no longer un-suppress each other.** The suppression was a flag cleared by whichever action finished first, so a save still in flight when the reader opened a page that loads on mount notified every listener on completion. It is a count now. (`lib/src/http/controllers/magic_starter_profile_controller.dart`)

- **The invitation-accept screen scrolls itself.** It was the one starter screen in the app shell with no scroll of its own, so a host whose content box does not scroll (the setting a host with `.stacked()` routes needs) handed it a bounded height, and on a short viewport the card overflowed with nothing to drag. It now sits in its own `SingleChildScrollView(primary: false)`, as `MSPageScaffold` screens do. (`lib/src/ui/views/teams/magic_starter_team_invitation_accept_view.dart`)
- **The invitation-accept screen scrolls itself.** It was the one starter screen in the app shell with no scroll of its own, so a content box that does not scroll (the default from this release, see Changed) handed it a bounded height, and on a short viewport the card overflowed with nothing to drag. It now sits in its own `SingleChildScrollView(primary: false)`, as `MSPageScaffold` screens do. (`lib/src/ui/views/teams/magic_starter_team_invitation_accept_view.dart`)

### Added

- **`resetQuietly()` on `MagicStarterProfileController` and `MagicStarterTeamController`**, the non-notifying reset those views call: errors cleared, state empty, no listener told. A host view that binds either controller and resets it in `onInit` should call it too. (`lib/src/http/controllers/`)

### Changed
### Breaking

- **The app shell's content box no longer scrolls; each routed page scrolls itself.** `MagicStarterLayoutTheme.contentClassName` defaults to `'flex-1 min-h-0'` and `contentScrollPrimary` to `false` (they were `'flex-1 overflow-y-auto'` and `true`). In a go_router shell the route child is the nested Navigator, so the old default scrolled the Navigator and laid its Overlay out under an unbounded height: a page left under a `.stacked()` route was never laid out again, and its second rebuild while hidden failed `_debugRelayoutBoundaryAlreadyMarkedNeedsLayout` in debug and left the tree inconsistent. Closes #160. (`lib/src/configuration/magic_starter_theme.dart`)

The starter's own screens already scroll themselves (`MSPageScaffold`, the invitation card), and two changed to keep doing so correctly: the notification screens now take the host page geometry inside their own scroll view rather than wrapped around it, where the padding would inset the viewport and clip a long list (`lib/src/routes/notification_routes.dart`), and the `starter:install` dashboard stub now scrolls itself (`assets/stubs/install/dashboard_view.stub`).

**Migrating:** a page of your own that relied on the shell to scroll it now renders cut off at the window. Put it through `MSPageScaffold` or wrap it in `SingleChildScrollView(primary: false)`; an app installed from an earlier stub has a `DashboardView` that needs the same. Or set the old pair back with `MagicStarter.useLayoutTheme(const MagicStarterLayoutTheme(contentClassName: 'flex-1 overflow-y-auto', contentScrollPrimary: true))`, accepting that stacked routes then carry the hazard above. `doc/basics/views-and-layouts.md` covers both.

- **`MagicStarterLayoutTheme.contentClassName` documents the stacked-route hazard.** The default `'flex-1 overflow-y-auto'` scrolls the shell's nested Navigator, which breaks a page left under a `.stacked()` route on its second hidden rebuild. The default is unchanged; the doc now says to set `'flex-1 min-h-0'` in any app that stacks routes under this layout. Refs #160. (`lib/src/configuration/magic_starter_theme.dart`)
**Lost:** tapping the iOS status bar no longer scrolls a shell page to the top. The shell's `Scaffold` scrolls the primary controller above the nested Navigator, which the old scrolling content box was attached to; every page's scroll now sits inside a route that scopes its own controller. Restoring it needs a status-bar handler below the Navigator, which is a separate change.

## [0.0.36] - 2026-09-23

Expand Down
299 changes: 152 additions & 147 deletions assets/stubs/install/dashboard_view.stub
Original file line number Diff line number Diff line change
Expand Up @@ -13,174 +13,179 @@ class DashboardView extends StatelessWidget {
Widget build(BuildContext context) {
final appName = Config.get('app.name', 'My App') ?? 'My App';

return WDiv(
className: 'w-full max-w-[480px] md:max-w-4xl mx-auto p-4 lg:p-8',
// Its own scroll: the app shell's content box does not scroll, so a
// page taller than the window scrolls itself.
return SingleChildScrollView(
primary: false,
child: WDiv(
className: '''
rounded-2xl bg-white dark:bg-gray-800
border border-gray-200 dark:border-gray-700
p-6 lg:p-8 flex flex-col items-center
''',
children: [
// --------------------------------------------------
// Hero Section
// --------------------------------------------------
WDiv(
className: '''
w-20 h-20 rounded-2xl
flex items-center justify-center
bg-gradient-to-br from-violet-500 to-purple-600
dark:from-violet-600 dark:to-purple-700
animate-bounce
''',
child: WIcon(
Icons.auto_awesome,
className: 'text-4xl text-white dark:text-gray-100',
),
),
const WSpacer(className: 'h-6'),
WText(
appName,
className: '''
text-2xl lg:text-3xl font-bold
text-gray-900 dark:text-white text-center
''',
),
const WSpacer(className: 'h-2'),
WText(
'Built with Magic Starter',
className: 'text-base text-gray-500 dark:text-gray-400',
),

const WSpacer(className: 'h-8'),

// --------------------------------------------------
// Quick-link cards
// --------------------------------------------------
WDiv(
className: 'w-full grid grid-cols-1 md:grid-cols-3 gap-3',
children: [
_buildLinkCard(
icon: Icons.menu_book,
title: 'Documentation',
description: 'Read the Magic Framework docs to get started.',
url: 'https://magic.fluttersdk.com',
),
_buildLinkCard(
icon: Icons.code,
title: 'GitHub',
description: 'Star the repo, report issues, or contribute code.',
url: 'https://github.com/fluttersdk/magic',
),
_buildLinkCard(
icon: Icons.terminal,
title: 'CLI Commands',
description: 'Run `magic --help` to see all available commands.',
url: 'https://magic.fluttersdk.com/cli',
),
],
),

const WSpacer(className: 'h-8'),

// --------------------------------------------------
// Footer
// --------------------------------------------------
WDiv(
className: 'flex flex-row items-center justify-center gap-1',
children: [
WText(
'Made with',
className: 'text-xs text-gray-400 dark:text-gray-500',
),
WDiv(
className: 'animate-pulse',
child: WText(
'❤️',
className: 'text-xs text-red-500 dark:text-red-400',
),
),
WText(
'by',
className: 'text-xs text-gray-400 dark:text-gray-500',
),
WAnchor(
onTap: () => Launch.url('https://anilcancakir.com'),
child: WText(
'Anılcan Çakır',
className: '''
text-xs font-medium text-primary
dark:text-primary-400
''',
),
),
],
),
],
),
);
}

/// Builds a single quick-link card with icon, title, description, and link.
Widget _buildLinkCard({
required IconData icon,
required String title,
required String description,
required String url,
}) {
return WDiv(
className: '''
flex flex-col p-4 rounded-xl
bg-gray-50 dark:bg-gray-700/30
border border-gray-100 dark:border-gray-700
''',
children: [
WDiv(
className: 'flex flex-row items-center gap-3 mb-2',
className: 'w-full max-w-[480px] md:max-w-4xl mx-auto p-4 lg:p-8',
child: WDiv(
className: '''
rounded-2xl bg-white dark:bg-gray-800
border border-gray-200 dark:border-gray-700
p-6 lg:p-8 flex flex-col items-center
''',
children: [
// --------------------------------------------------
// Hero Section
// --------------------------------------------------
WDiv(
className: '''
p-2 rounded-lg
bg-primary/10 dark:bg-primary-900/30
w-20 h-20 rounded-2xl
flex items-center justify-center
bg-gradient-to-br from-violet-500 to-purple-600
dark:from-violet-600 dark:to-purple-700
animate-bounce
''',
child: WIcon(
icon,
className: 'text-lg text-primary dark:text-primary-400',
Icons.auto_awesome,
className: 'text-4xl text-white dark:text-gray-100',
),
),
const WSpacer(className: 'h-6'),
WText(
title,
appName,
className: '''
text-base font-semibold
text-gray-900 dark:text-white
text-2xl lg:text-3xl font-bold
text-gray-900 dark:text-white text-center
''',
),
const WSpacer(className: 'h-2'),
WText(
'Built with Magic Starter',
className: 'text-base text-gray-500 dark:text-gray-400',
),

const WSpacer(className: 'h-8'),

// --------------------------------------------------
// Quick-link cards
// --------------------------------------------------
WDiv(
className: 'w-full grid grid-cols-1 md:grid-cols-3 gap-3',
children: [
_buildLinkCard(
icon: Icons.menu_book,
title: 'Documentation',
description: 'Read the Magic Framework docs to get started.',
url: 'https://magic.fluttersdk.com',
),
_buildLinkCard(
icon: Icons.code,
title: 'GitHub',
description: 'Star the repo, report issues, or contribute code.',
url: 'https://github.com/fluttersdk/magic',
),
_buildLinkCard(
icon: Icons.terminal,
title: 'CLI Commands',
description: 'Run `magic --help` to see all available commands.',
url: 'https://magic.fluttersdk.com/cli',
),
],
),

const WSpacer(className: 'h-8'),

// --------------------------------------------------
// Footer
// --------------------------------------------------
WDiv(
className: 'flex flex-row items-center justify-center gap-1',
children: [
WText(
'Made with',
className: 'text-xs text-gray-400 dark:text-gray-500',
),
WDiv(
className: 'animate-pulse',
child: WText(
'❤️',
className: 'text-xs text-red-500 dark:text-red-400',
),
),
WText(
'by',
className: 'text-xs text-gray-400 dark:text-gray-500',
),
WAnchor(
onTap: () => Launch.url('https://anilcancakir.com'),
child: WText(
'Anılcan Çakır',
className: '''
text-xs font-medium text-primary
dark:text-primary-400
''',
),
),
],
),
],
),
WText(
description,
className: 'text-sm text-gray-500 dark:text-gray-400 mb-3',
),
WAnchor(
onTap: () => Launch.url(url),
child: WDiv(
className: 'flex flex-row items-center gap-1',
);
}

/// Builds a single quick-link card with icon, title, description, and link.
Widget _buildLinkCard({
required IconData icon,
required String title,
required String description,
required String url,
}) {
return WDiv(
className: '''
flex flex-col p-4 rounded-xl
bg-gray-50 dark:bg-gray-700/30
border border-gray-100 dark:border-gray-700
''',
children: [
WDiv(
className: 'flex flex-row items-center gap-3 mb-2',
children: [
WText(
'Learn more',
WDiv(
className: '''
text-sm font-medium
text-primary dark:text-primary-400
p-2 rounded-lg
bg-primary/10 dark:bg-primary-900/30
''',
child: WIcon(
icon,
className: 'text-lg text-primary dark:text-primary-400',
),
),
WIcon(
Icons.arrow_forward_outlined,
className: 'text-sm text-primary dark:text-primary-400',
WText(
title,
className: '''
text-base font-semibold
text-gray-900 dark:text-white
''',
),
],
),
),
],
WText(
description,
className: 'text-sm text-gray-500 dark:text-gray-400 mb-3',
),
WAnchor(
onTap: () => Launch.url(url),
child: WDiv(
className: 'flex flex-row items-center gap-1',
children: [
WText(
'Learn more',
className: '''
text-sm font-medium
text-primary dark:text-primary-400
''',
),
WIcon(
Icons.arrow_forward_outlined,
className: 'text-sm text-primary dark:text-primary-400',
),
],
),
),
],
),
);
}
}
Loading
Loading